diff --git a/.circleci/config.yml b/.circleci/config.yml index 63644ec467..34f1050f6d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -88,7 +88,7 @@ jobs: python -m venv env/ . env/bin/activate python -m pip install --upgrade pip - pip install '.[dev]' + python -m pip install '.[dev]' - save_cache: name: Save cached venv @@ -103,10 +103,10 @@ jobs: pip install -e '.[dev]' - run: - name: Instantiate Mock Wallet & Protos + name: Instantiate Mock Wallet command: | . env/bin/activate - ./scripts/create_wallet.sh && ./scripts/build_protos.sh + ./scripts/create_wallet.sh # TODO: Update test durations on different runs - run: @@ -247,7 +247,7 @@ workflows: - build-and-test: matrix: parameters: - python-version: ["3.8.14", "3.9.13", "3.10.6"] + python-version: ["3.9.13", "3.10.6", "3.11.4"] - unit-tests-all-python-versions: requires: - build-and-test diff --git a/.coveragerc b/.coveragerc index 80dd6040ed..b0e422abef 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,15 +1,7 @@ [run] omit = - ./nuclei/* + ./nuclei/* ./routers/* ./setup.py ./tests/* - ./bittensor/_logging/init.py - ./bittensor/_neuron/* - ./bittensor/_cli/__init__.py - ./bittensor/_cli/cli_impl.py - ./bittensor/_dendrite/dendrite_mock.py - ./bittensor/_dataset/dataset_mock.py - ./bittensor/_metagraph/metagraph_mock.py - ./benchmarks/* ./env/* diff --git a/.gitignore b/.gitignore index 569070bbfe..5cc3b79913 100644 --- a/.gitignore +++ b/.gitignore @@ -4,14 +4,12 @@ *$py.class *.pyc +# Remove notebooks. +*.ipynb + # weigths and biases wandb/ -# benchmark results -benchmarks/results/ -benchmarks/results/* -!benchmarks/results - *.csv *.torch *.pt diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e58a027ba..9b10736b03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,14 +37,13 @@ ## 5.3.1 / 2023-07-06 -## What's Changed -* bump bittensor-wallet req, update cryptography security req by @@ifrit98 in [91d13b0](https://github.com/opentensor/bittensor/commit/91d13b0fa711621cbf823708d4368b1b387e42c4) -* Fixes Discord Link Issue #1442 by @camfairchild in [54d6248](https://github.com/opentensor/bittensor/commit/54d62487d4cb59e0b5edcd53acdca013108d155b) -* move mocks to bittensor_wallet package by @camfairchild in https://github.com/opentensor/bittensor/pull/1441 -* Bump bittensor-wallet version to 0.0.4 - -**Full Changelog**: https://github.com/opentensor/bittensor/compare/v5.3.0...v5.3.1 + ## What's Changed + * bump bittensor-wallet req, update cryptography security req by @@ifrit98 in [91d13b0](https://github.com/opentensor/bittensor/commit/91d13b0fa711621cbf823708d4368b1b387e42c4) + * Fixes Discord Link Issue #1442 by @camfairchild in [54d6248](https://github.com/opentensor/bittensor/commit/54d62487d4cb59e0b5edcd53acdca013108d155b) + * move mocks to bittensor_wallet package by @camfairchild in https://github.com/opentensor/bittensor/pull/1441 + * Bump bittensor-wallet version to 0.0.4 + **Full Changelog**: https://github.com/opentensor/bittensor/compare/v5.3.0...v5.3.1 ## 5.3.0 / 2023-07-04 diff --git a/Dockerfile b/Dockerfile index 406611c0a7..15081dbe70 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # syntax=docker/dockerfile:1 -FROM pytorch/pytorch:1.13.1-cuda11.6-cudnn8-devel +FROM pytorch/pytorch:2.0.1-cuda11.7-cudnn8-devel LABEL bittensor.image.authors="bittensor.com" \ bittensor.image.vendor="Bittensor" \ @@ -9,8 +9,8 @@ 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="1.13.1" -LABEL bittensor.dependencies.versions.cuda="11.6" +LABEL bittensor.dependencies.versions.torch="2.0.1" +LABEL bittensor.dependencies.versions.cuda="11.7" ARG DEBIAN_FRONTEND=noninteractive #nvidia key migration diff --git a/README.md b/README.md index 7a33c49c82..c1552a29c2 100644 --- a/README.md +++ b/README.md @@ -13,31 +13,12 @@ -This repository contains Bittensor's Python API, which can be used for the following purposes: - -1. Querying the Bittensor network as a client. -2. Running and building Bittensor miners. (Validators are now at [openvalidators](https://github.com/opentensor/validators)). -3. Pulling network state information. -4. Managing TAO wallets, balances, transfers, etc. - Bittensor is a mining network, similar to Bitcoin, that includes built-in incentives designed to encourage computers to provide access to machine learning models in an efficient and censorship-resistant manner. These models can be queried by users seeking outputs from the network, for instance; generating text, audio, and images, or for extracting numerical representations of these input types. Under the hood, Bittensor’s *economic market*, is facilitated by a blockchain token mechanism, through which producers (***miners***) and the verification of the work done by those miners (***validators***) are rewarded. Miners host, train or otherwise procure machine learning systems into the network as a means of fulfilling the verification problems defined by the validators, like the ability to generate responses from prompts i.e. “What is the capital of Texas?. The token based mechanism under which the miners are incentivized ensures that they are constantly driven to make their knowledge output more useful, in terms of speed, intelligence and diversity. The value generated by the network is distributed directly to the individuals producing that value, without intermediarias. Anyone can participate in this endeavour, extract value from the network, and govern Bittensor. The network is open to all participants, and no individual or group has full control over what is learned, who can profit from it, or who can access it. To learn more about Bittensor, please read our [paper](https://bittensor.com/whitepaper). -# Usage -There are currently three primary ways to interact with Bittensor via this repository: - -1. [Developers](#Developers) - - Those attempting to interact with the Bittensor Network to solve tasks. - -2. [Miners](#Miners) - - Individuals, researchers and developers seeking to contribute value into Bittensor and get paid in mining rewards. - -3. [Validators](#Validators) - - TAO holders who are looking to govern Bittensor directly. - # Install There are three ways to install Bittensor @@ -66,7 +47,7 @@ $ btcli --help ``` or using python ```python -import bittensor as bt +import bittensor ``` # Wallets @@ -76,25 +57,26 @@ Wallets are the core ownership and identity technology around which all function Wallets can be created in two ways. 1. Using the python-api ```python -import bittensor as bt -wallet = bt.wallet() +import bittensor +wallet = bittensor.wallet() wallet.create_new_coldkey() wallet.create_new_hotkey() print (wallet) "Wallet (default, default, ~/.bittensor/wallets/)" ``` 2. Or using btcli +> Use the subcommand `wallet` or it's alias `w`: ```bash -$ btcli new_coldkey +$ btcli wallet new_coldkey Enter wallet name (default): IMPORTANT: Store this mnemonic in a secure (preferably offline place), as anyone who has possesion of this mnemonic can use it to regenerate the key and access your tokens. The mnemonic to the new coldkey is: **** *** **** **** ***** **** *** **** **** **** ***** ***** You can use the mnemonic to recreate the key in case it gets lost. The command to use to regenerate the key using this mnemonic is: - btcli regen_coldkey --mnemonic post maid erode shy captain verify scan shoulder brisk mountain pelican elbow + btcli w regen_coldkey --mnemonic post maid erode shy captain verify scan shoulder brisk mountain pelican elbow -$ btcli new_hotkey +$ btcli wallet new_hotkey Enter wallet name (default): d1 Enter hotkey name (default): @@ -102,9 +84,9 @@ $ btcli new_hotkey The mnemonic to the new hotkey is: **** *** **** **** ***** **** *** **** **** **** ***** ***** You can use the mnemonic to recreate the key in case it gets lost. The command to use to regenerate the key using this mnemonic is: - btcli regen_hotkey --mnemonic total steak hour bird hedgehog trim timber can friend dry worry text + btcli w regen_hotkey --mnemonic total steak hour bird hedgehog trim timber can friend dry worry text ``` -In both cases you should be able to view your keys by navigating to ~/.bittensor/wallets or viewed by running ```btcli list``` +In both cases you should be able to view your keys by navigating to ~/.bittensor/wallets or viewed by running ```btcli wallet list``` ```bash $ tree ~/.bittensor/ .bittensor/ # Bittensor, root directory. @@ -117,202 +99,105 @@ $ tree ~/.bittensor/ ``` Your default wallet ```Wallet (default, default, ~/.bittensor/wallets/)``` is always used unless you specify otherwise. Be sure to store your mnemonics safely. If you lose your password to your wallet, or the access to the machine where the wallet is stored, you can always regenerate the coldkey using the mnemonic you saved from above. ```bash -$ btcli regen_coldkey --mnemonic **** *** **** **** ***** **** *** **** **** **** ***** ***** +$ btcli wallet regen_coldkey --mnemonic **** *** **** **** ***** **** *** **** **** **** ***** ***** ``` -# Developers +## Using the cli +The Bittensor command line interface (`btcli`) is the primary command line tool for interacting with the Bittensor network. It can be used to deploy nodes, manage wallets, stake/unstake, nominate, transfer tokens, and more. -Without participating directly in Bittensor’s incentive mechanism, i.e. before holding TAO, becoming a miner, or being a validator, the only way to access Bittensor is by relaying queries through models who have opened exterior access to developers. By default, Bittensor’s api uses the Opentensor Foundation’s endpoint which acts as a bridge onto the network. To access other validators endpoints you must specify their hotkey key, found by running ```btcli list_delegates``` -```python -import bittensor as bt - -# Query through the foundation endpoint. -print ( bt.prompt( "Heraclitus was a ") ) -'Greek philosopher known for his doctrine of change and the famous quote, "No man ever steps in the same river twice."' - -# The API also contains BittensorLLM which can be integrated with langchain. -import bittensor as bt -llm = bt.BittensorLLM() -llm( 'prompt me' ) - -# Return multiple responses for a single prompt. -bt.prompt( "What should I do today?", return_all = True ) -[ - 'You should buy a boat.', - 'As a language model I cannot answer that question.', - 'You should write in your journal.', - 'Mine bittensor.' - ... -] - -# Specify a separate entrypoint based on the delegate key. -print ( bt.prompt( "Heraclitus was a ", hotkey = "5F4tQyWrhfGVcNhoqeiNsR6KjD4wMZ2kfhLj4oHYuyHbZAc3" ) ) -'Greek philosopher known for his doctrine of change and the famous quote, "No man ever steps in the same river twice."' -``` - -Validators can access Bittensor directly without the need to bridge requests. -```python -import bittensor as bt -wallet = bt.wallet() # Your validator wallet. -metagraph = bt.metagraph( netuid = 1 ) # Get state from subnetwork 1. -dendrite = bt.text_prompting( keypair = wallet.hotkey, axon = metagraph.axons[ 10 ] ) # Connection to uid 10 -dendrite.forward( roles = ['system', 'user'], messages = ['you are my financial advisor', 'should I buy a boat?'] ) -``` +### Basic Usage -# Miners -The mining challenge on Bittensor is divided into ***subnetworks*** where miners within each subnet are incentivized to contribute distinct forms of value determined by the verification mechanism that that subnetwork’s Validators are running. You can view a list of these subnetworks with ```btcli list_subnets``` +To get the list of all the available commands and their descriptions, you can use: ```bash -$ btcli list_subnets - NETUID NEURONS MAX_N DIFFICULTY TEMPO CON_REQ EMISSION BURN(τ) - 1 691 1.02 K 198.08 T 99 None 28.44% τ4.75710 - 3 4096 4.10 K 320.81 T 99 None 71.56% τ1.00000 - 2 5120 - - Description: - # NETUID: A unique network index on Bittensor - # NEURONS: The number of uid slots taken by miners - # MAX_N: The total allowed slots on a subnetwork - # DIFFICULTY: The difficulty of the POW registration challenge required to win a slot. - # TEMPO: The number of blocks before new tokens are distributed. - # CON_REQ: The list of subnetworks that a miner must enter before entering this network. - # EMISSION: The proportion of the total token emission which flows through this network. - # BURN: The recycle burn cost to enter this network. -``` +btcli --help -Each subnetwork contains a set of UIDs, or slots, into which miners must ***register*** into before they are considered for evaluation by validators in the network and thus mine TAO. These slots fill up through continuous registrations and miners are dropped from the network based on performance. Each time a new hotkey is registered to the subnet, the lowest ranked miner is removed from the subnet. The process of registering a miner is competitive, and uses two mutually adaptive method to determine the price to entry, those are: +usage: btcli -1. Proof of work based registration. -```bash -$ btcli register --netuid -``` -NOTE: It is suggested that you use a Nvidia GPU to register. To do this, you can install Cubit to enable registrations via your GPU for a faster hash rate. -```bash -(optional): pip install git+https://github.com/opentensor/cubit.git@v1.1.2 -``` +bittensor cli v{bittensor.__version__} -2. and TAO recycling registration -```bash -$ btcli recycle_register --netuid +commands: + subnets (s, subnet) - Commands for managing and viewing subnetworks. + root (r, roots) - Commands for managing and viewing the root network. + wallet (w, wallets) - Commands for managing and viewing wallets. + stake (st, stakes) - Commands for staking and removing stake from hotkey accounts. + sudo (su, sudos) - Commands for subnet management. + legacy (l) - Miscellaneous commands. ``` -POW registration is recommmended for miners contributing raw compute power to bittensor and are seeking a method attaining a slot without the token initially. Recycle registration is recommended for anyone seeking to attain slots and already has a small amount of TAO at their disposal. In both cases, the registration requires a ```--netuid``` parameter which specifies which subnetwork the miner is entering. Once they registered the miner attains a slot specified by their UID, this UID is thiers to mine under. To view your slot after registration, run the overview command -```bash -$ btcli overview --netuid -``` +### Example Commands -Registered miners are free to select from variety of pre-written miners or to write their own using the python api. You can find these miners by cloning this repository locally. -```bash -$ git clone https://github.com/opentensor/bittensor.git - bittensor/ # This repo. - neurons/ # Miners across all subnetworks. - text_prompting/ # Miners for the text_prompting subnetwork. - miners/ # Miners. - GPT4ALL/ # The root folder for the GPT4ALL miner. - neuron.py # GPT4ALL miner main script. - requirements.txt # GPT4ALL requirements. - README.md # GPT4ALL instructions. - ... -``` -For instance, you can run the GPT4ALL miner on subnetwork 1 as follows. Note: it is recommended to run most miners on machines with a GPU. In the future bittensor/neurons is likely to expand into its own repository. +#### Viewing Senate Proposals ```bash -$ python3 -m pip install -r bittensor/neurons/text_prompting/miners/GPT4ALL/requirements.txt -$ python3 bittensor/neurons/text_prompting/miners/GPT4ALL/neuron.py --netuid 1 +btcli root proposals ``` -# Validators -Network Validation is open to participants who hold TAO. The validation mechanims uses a dual proof-of-stake, proof-of-work mechanism called Yuma Consensus which you can read about [here](https://bittensor.com/whitepaper). Yuma consensus rewards the agreement between the evaluations of miner-value produced by validators across each subnetwork. Because each subnetwork task is distinct this requires a separate implementation of the each validator for each network. - -Before becoming a validator you will need to register a slot as described above in the mining section. Keys are automatically considered Validators in each subnetwork if the registered hotkey is a member of the top 128 keys ranked by total stake. Stake determines the weight given to the value estimations of your validator in Yuma Consensus. There are exclusively two ways to attain stake on your validator. - -1. By staking the funds yourself +#### Viewing Senate Members ```bash -$ btcli stake --help # To add funds to the staking account associated with your wallet. +btcli root list_delegates ``` -2. Or by attracting delegated stake +#### Viewing Proposal Votes ```bash -$ btcli nominate --help # to become a key available for delegated stake -$ btcli delegate --help # for others to delegate stake to your wallet. +btcli root senate_vote --proposal=[PROPOSAL_HASH] ``` -Bittensor's API is designed to allow Validators to write their own validation mechanisms and express their own subjective prefrences about what the network should learn. However, going too far outside consensus reduces the rewards validators attain while performing validation. To ensure your validator remains in alignment with others, please see the `openvalidators` repo [here](https://github.com/opentensor/validators). -# Using the CLI - -The Bittensor command line interface (btcli) comes installed with this repository. It is the primary command line tool to deploy, analyze, and interface with the Bittensor network. It can be used to transfer tao, stake, unstake, nominate, delegate, and more. You can use btcli --help command as follows to see a full list of commands +#### Registering for Senate ```bash -$ btcli --help - help Displays the help. - list List wallets. - stake Stake to your hotkey accounts. - update Updates your bittensor installation. - inspect Inspect a wallet cold, hot pair - weights Show the weights from chain. - unstake Unstake from hotkey accounts. - overview Show registered account overview. - register Register a wallet to a network. - transfer Transfer Tao between accounts. - nominate Become a delegate on the network - new_hotkey Creates a new hotkey for running a miner under the specified path. - metagraph Show the network graph. - new_coldkey Creates a new coldkey for containing balance under the specified path. - my_delegates Show all delegates where I am delegating a positive amount of stake. - list_subnets List all subnets on the network. - regen_hotkey Regenerates a hotkey from a passed mnemonic. - regen_coldkey Regenerates a coldkey from a passed value. - delegate Delegate Stake to an account. - undelegate Undelegate Stake from an account. - list_delegates List all delegates on the network. - regen_coldkeypub Regenerates a public coldkey from the public part of the coldkey. - recycle_register Register a wallet to a network. - senate View senate and it's members - proposals View active triumvirate proposals and their status - proposal_votes View an active proposal's votes by address. - senate_register Register as a senate member to participate in proposals - senate_leave Discard senate membership in the governance protocol - senate_vote Vote on an active proposal by hash. - - optional arguments: - -h, - --help Show this help message and exit - --config CONFIG If set, defaults are overridden by passed file. - --strict If flagged, config will check that only exact arguemnts have been set. +btcli root register ``` -# Senate -List all active proposals for the Senate to vote on. Usage: btcli proposals +#### Leaving Senate ```bash -btcli proposals +btcli root undelegate ``` -View Senate -View all delegates currently registered to Senate. Usage: btcli senate +#### Voting in Senate ```bash -btcli senate +btcli root senate_vote --proposal=[PROPOSAL_HASH] ``` -Proposal Votes -Inspect the votes for a single proposal. Usage: btcli proposal_votes [OPTIONS] +#### Miscellaneous Commands ```bash -btcli proposal_votes --proposal=[PROPOSAL_HASH] +btcli legacy update +btcli legacy faucet ``` -Senate Register -Elect to join the Senate with your nominated hotkey. Usage: btcli senate_register [OPTIONS] +#### Managing Subnets ```bash -btcli senate_register +btcli subnets list +btcli subnets create ``` -Senate Leave -Disown your membership of a Senate seat with your nominated hotkey. Usage: btcli senate_leave [OPTIONS] +#### Managing Wallets ```bash -btcli senate_leave +btcli wallet list +btcli wallet transfer ``` -Senate Vote -Participate in a triumvirate proposal by voting with your senate hotkey. Usage: btcli senate_vote [OPTIONS] +### Note + +Please replace the subcommands and arguments as necessary to suit your needs, and always refer to `btcli --help` or `btcli --help` for the most up-to-date and accurate information. + +For example: ```bash -btcli senate_vote --proposal=[PROPOSAL_HASH] +btcli subnets --help + +usage: btcli subnets [-h] {list,metagraph,lock_cost,create,register,recycle_register,hyperparameters} ... + +positional arguments: + {list,metagraph,lock_cost,create,register,recycle_register,hyperparameters} + Commands for managing and viewing subnetworks. + list List all subnets on the network + metagraph View a subnet metagraph information. + lock_cost Return the lock cost to register a subnet + create Create a new bittensor subnetwork on this chain. + register Register a wallet to a network. + recycle_register Register a wallet to a network. + hyperparameters View subnet hyperparameters + +options: + -h, --help show this help message and exit ``` # The Bittensor Package @@ -320,9 +205,9 @@ The bittensor package contains data structures for interacting with the bittenso Wallet: Interface over locally stored bittensor hot + coldkey styled wallets. ```python -import bittensor as bt +import bittensor # Bittensor's wallet maintenance class. -wallet = bt.wallet() +wallet = bittensor.wallet() # Access the hotkey wallet.hotkey # Access the coldkey @@ -334,9 +219,9 @@ wallet.coldkey.sign( data ) Subtensor: Interfaces with bittensor's blockchain and can perform operations like extracting state information or sending transactions. ```python -import bittensor as bt +import bittensor # Bittensor's chain interface. -subtensor = bt.subtensor() +subtensor = bittensor.subtensor() # Get the chain block subtensor.get_current_block() # Transfer Tao to a destination address. @@ -347,9 +232,9 @@ subtensor.register( wallet = wallet, netuid = 1 ) Metagraph: Encapsulates the chain state of a particular subnetwork at a specific block. ```python -import bittensor as bt +import bittensor # Bittensor's chain state object. -metagraph = bt.metagraph( netuid = 1 ) +metagraph = bittensor.metagraph( netuid = 1 ) # Resync the graph with the most recent chain state metagraph.sync() # Get the list of stake values @@ -366,49 +251,104 @@ metagraph.save() metagraph.load() ``` -Axon: Maintains a queryable endpoint for accepting messages on the wire. +Synapse: Responsible for defining the protocol definition betwee axon servers and dendrite clients ```python -import bittensor as bt -# Instantiate a Bittensor endpoint. -axon = bt.axon( wallet = wallet, metagraph = metagraph ) -# Start servicing messages on the wire. -axon.start() -# Register this axon on a subnetwork -subtensor.serve_axon( netuid = 1, axon = axon ) -# Turn off the axon. -axon.stop() +class Topk( bittensor.Synapse ): + topk: int = 2 # Number of "top" elements to select + input: bittensor.Tensor = pydantic.Field(..., allow_mutation=False) # Ensure that input cannot be set on the server side. + v: bittensor.Tensor = None + i: bittensor.Tensor = None + +def topk( synapse: Topk ) -> Topk: + v, i = torch.topk( synapse.input.deserialize(), k = synapse.topk ) + synapse.v = bittensor.Tensor.serialize( v ) + synapse.i = bittensor.Tensor.serialize( i ) + return synapse + +# Attach the forward function to the axon and start. +axon = bittensor.axon().attach( topk ).start() ``` -Synapse: Implements the wire protocol required to service requests from validators on a subnetwor +Axon: Serves Synapse protocols with custom blacklist, priority and verify functions. + +```python +import bittensor + +class MySyanpse( bittensor.Synapse ): + input: int = 1 + output: int = None + +# Define a custom request forwarding function +def forward( synapse: MySyanpse ) -> MySyanpse: + # Apply custom logic to synapse and return it + synapse.output = 2 + return synapse + +# Define a custom request verification function +def verify_my_synapse( synapse: MySyanpse ): + # Apply custom verification logic to synapse + # Optionally raise Exception + +# Define a custom request blacklist fucntion +def blacklist_my_synapse( synapse: MySyanpse ) -> bool: + # Apply custom blacklist + # return False ( if non blacklisted ) or True ( if blacklisted ) + +# Define a custom request priority fucntion +def prioritize_my_synape( synapse: MySyanpse ) -> float: + # Apply custom priority + return 1.0 + +# Initialize Axon object with a custom configuration +my_axon = bittensor.axon(config=my_config, wallet=my_wallet, port=9090, ip="192.0.2.0", external_ip="203.0.113.0", external_port=7070) + +# Attach the endpoint with the specified verification and forwarding functions +my_axon.attach( + forward_fn = forward_my_synapse, + verify_fn=verify_my_synapse, + blacklist_fn = blacklist_my_synapse, + priority_fn = prioritize_my_synape +).start() +``` + +Dendrite: Inheriting from PyTorch's Module class, represents the abstracted implementation of a network client module designed +to send requests to those endpoint to recieve inputs. +Example: ```python -import bittensor as bt - -# Netuid 1 specification. -class Synapse( bittensor.TextPromptingSynapse ): - - # Return the priority of the request, larger numbers are serviced by the endpoint first. - def priority(self, forward_call: "bittensor.TextPromptingForwardCall") -> float: return 0.0 - - # Return True if the request will not be serviced by this miner endpoint. - def blacklist(self, forward_call: "bittensor.TextPromptingForwardCall") -> Union[ Tuple[bool, str], bool ]: return False - - # Accept and optionally apply the feedback from a validator on the network. - def backward( self, messages: List[Dict[str, str]], response: str, rewards: torch.FloatTensor ) -> str: pass - - # Return an output which will be rewarded highly by validators on this subnetwork. - def forward(self, messages: List[Dict[str, str]]) -> str: return "hello im a chat bot." - -# Attach this synapse to the running axon. -synapse = Synapse( axon = axon ) +dendrite_obj = dendrite( wallet = bittensor.wallet() ) +# pings the axon endpoint +await d( ) +# ping multiple axon endpoints +await d( [] ) +# Send custom synapse request to axon. +await d( bittensor.axon(), bittensor.Synapse() ) +# Query all metagraph objects. +await d( meta.axons, bittensor.Synapse() ) ``` -Dendrite: Packages and sends messages to synapses over the wire. -```python -import bittensor as bt -# Connect to the axon running on slot 10, use the wallet to sign messages. -dendrite = bt.text_prompting( keypair = wallet.hotkey, axon = metagraph.axons[10] ) -# Send a prompt to this endpoint -dendrite.forward( roles = ['user'], messages = ['what are you?'] ) +## Setting weights on root network +Use the `root` subcommand to access setting weights on the network across subnets. + +```bash +btcli root weights --wallet.name --wallet.hotkey +Enter netuids (e.g. 0, 1, 2 ...): +# Here enter your selected netuids to wet weights on +1, 2 + +>Enter weights (e.g. 0.09, 0.09, 0.09 ...): +# These do not need to sum to 1, we do normalization on the backend. +# Values must be > 0 +0.5, 10 + +Normalized weights: + tensor([ 0.5000, 10.0000]) -> tensor([0.0476, 0.9524]) + +Do you want to set the following root weights?: + weights: tensor([0.0476, 0.9524]) + uids: tensor([1, 2])? [y/n]: +y + +⠏ 📡 Setting root weights on test ... ``` ## Release diff --git a/VERSION b/VERSION index 86f2a61e3d..09b254e90c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -5.3.4 \ No newline at end of file +6.0.0 diff --git a/auto_update.sh b/auto_update.sh deleted file mode 100755 index b64aa6c3bb..0000000000 --- a/auto_update.sh +++ /dev/null @@ -1,88 +0,0 @@ -#!/bin/bash - -# Initialize variables -script="" -proc_name="auto_run_bittensor" -args=() - -# Check if pm2 is installed -if ! command -v pm2 &> /dev/null -then - echo "pm2 could not be found. To install see: https://pm2.keymetrics.io/docs/usage/quick-start/" - exit 1 -fi - -# Parse command line arguments -while [[ "$#" -gt 0 ]]; do - case $1 in - --script) script="$2"; shift ;; - --name) name="$2"; shift ;; - --*) args+=("$1=$2"); shift ;; - *) echo "Unknown parameter passed: $1"; exit 1 ;; - esac - shift -done - -# Check if script argument was provided -if [[ -z "$script" ]]; then - echo "The --script argument is required." - exit 1 -fi - -branch=$(git branch --show-current) # get current branch. -echo watching branch: $branch -echo pm2 process name: $proc_name - -# Get the current file hash -current_hash=$(git log -n 1 --pretty=format:%H -- $script) -echo current_hash: $current_hash - -# Check if script is already running with pm2 -if pm2 status | grep -q $proc_name; then - echo "The script is already running with pm2. Stopping and restarting..." - pm2 delete $proc_name -fi - -# Run the Python script with the arguments using pm2 -echo "Running $script with the following arguments with pm2:" -echo "${args[@]}" -pm2 start "$script" --name $proc_name --interpreter python3 -- "${args[@]}" - -while true; do - # Fetch the latest changes from the repository - git fetch origin $branch - - # Get the latest file hash - latest_hash=$(git log -n 1 --pretty=format:%H -- origin $branch -- $script) - echo "current script hash:" "$current_hash" - echo "latest script hash:" "$latest_hash" - - # If the file has been updated - if [ "$current_hash" != "$latest_hash" ]; then - echo "The file has been updated. Updating the local copy." - - # Pull the latest changes - git pull - - # Update the current file hash - current_hash=$latest_hash - - # Check if script is already running with pm2 - if pm2 status | grep -q $proc_name; then - echo "The script is already running with pm2. Stopping and restarting..." - pm2 delete $proc_name - fi - - # Run the Python script with the arguments using pm2 - echo "Running $script with the following arguments with pm2:" - echo "${args[@]}" - pm2 start "$script" --name $proc_name --interpreter python3 -- "${args[@]}" - - echo "" - else - echo "" - fi - - # Wait for a while before the next check - sleep 5 -done diff --git a/bin/btcli b/bin/btcli index 68bb4fecd7..07581cd40e 100755 --- a/bin/btcli +++ b/bin/btcli @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python import sys diff --git a/bittensor/__init__.py b/bittensor/__init__.py index 9a7d8fb27f..71dc689c4d 100644 --- a/bittensor/__init__.py +++ b/bittensor/__init__.py @@ -1,5 +1,7 @@ # The MIT License (MIT) # Copyright © 2021 Yuma Rao +# Copyright © 2022-2023 Opentensor Foundation +# Copyright © 2023 Opentensor Technologies Inc # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated # documentation files (the “Software”), to deal in the Software without restriction, including without limitation @@ -15,20 +17,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. -import torch -from typing import Union, List, Dict from rich.console import Console from rich.traceback import install -from prometheus_client import Info -from langchain.llms.base import LLM -from typing import Optional, List, Mapping, Any, Tuple +# Install and apply nest asyncio to allow the async functions +# to run in a .ipynb import nest_asyncio nest_asyncio.apply() # Bittensor code and protocol version. -__version__ = "5.3.4" +__version__ = "6.0.0" version_split = __version__.split(".") __version_as_int__ = ( (100 * int(version_split[0])) @@ -37,11 +36,6 @@ ) __new_signature_version__ = 360 -# Turn off rich console locals trace. -from rich.traceback import install - -install(show_locals=False) - # Rich console. __console__ = Console() __use_console__ = True @@ -51,19 +45,32 @@ def turn_console_off(): + global __use_console__ + global __console__ from io import StringIO __use_console__ = False __console__ = Console(file=StringIO(), stderr=False) -# Vocabulary dimension. -# __vocab_size__ = len( tokenizer ) + len( tokenizer.additional_special_tokens) + 100 # Plus 100 for eventual token size increase. -__vocab_size__ = 50258 +def turn_console_on(): + global __use_console__ + global __console__ + __use_console__ = True + __console__ = Console() + + +turn_console_off() + + +# Logging helpers. +def trace(on: bool = True): + logging.set_trace(on) + + +def debug(on: bool = True): + logging.set_debug(on) -# Tensor dimension. -# NOTE (const): if/when this increases peers must be responsible for trimming or expanding output to this size. -__network_dim__ = 1024 # All network responses have shape = [ __batch_size__, __sequence_dim__, __network_dim__ ] # Substrate chain block time (seconds). __blocktime__ = 12 @@ -82,22 +89,6 @@ def turn_console_off(): __networks__ = ["local", "finney"] -__datasets__ = [ - "ArXiv", - "BookCorpus2", - "Books3", - "DMMathematics", - "EnronEmails", - "EuroParl", - "Gutenberg_PG", - "HackerNews", - "NIHExPorter", - "OpenSubtitles", - "PhilPapers", - "UbuntuIRC", - "YoutubeSubtitles", -] - __finney_entrypoint__ = "wss://entrypoint-finney.opentensor.ai:443" __finney_test_entrypoint__ = "wss://test.finney.opentensor.ai:443/" @@ -111,9 +102,6 @@ def turn_console_off(): __rao_symbol__: str = chr(0x03C1) -# Mock Testing Constant -__GLOBAL_MOCK_STATE__ = {} - # Block Explorers map network to explorer url ## Must all be polkadotjs explorer urls __network_explorer_map__ = { @@ -127,296 +115,118 @@ def turn_console_off(): "types": { "Balance": "u64", # Need to override default u128 }, + "runtime_api": { + "NeuronInfoRuntimeApi": { + "methods": { + "get_neuron_lite": { + "params": [ + { + "name": "netuid", + "type": "u16", + }, + { + "name": "uid", + "type": "u16", + }, + ], + "type": "Vec", + }, + "get_neurons_lite": { + "params": [ + { + "name": "netuid", + "type": "u16", + }, + ], + "type": "Vec", + }, + } + }, + "StakeInfoRuntimeApi": { + "methods": { + "get_stake_info_for_coldkey": { + "params": [ + { + "name": "coldkey_account_vec", + "type": "Vec", + }, + ], + "type": "Vec", + }, + "get_stake_info_for_coldkeys": { + "params": [ + { + "name": "coldkey_account_vecs", + "type": "Vec>", + }, + ], + "type": "Vec", + }, + }, + }, + "ValidatorIPRuntimeApi": { + "methods": { + "get_associated_validator_ip_info_for_subnet": { + "params": [ + { + "name": "netuid", + "type": "u16", + }, + ], + "type": "Vec", + }, + }, + }, + "SubnetInfoRuntimeApi": { + "methods": { + "get_subnet_hyperparams": { + "params": [ + { + "name": "netuid", + "type": "u16", + }, + ], + "type": "Vec", + } + } + }, + "SubnetRegistrationRuntimeApi": { + "methods": {"get_network_registration_cost": {"params": [], "type": "u64"}} + }, + }, } -# --- Prometheus --- -__prometheus_version__ = "0.1.0" -prometheus_version__split = __prometheus_version__.split(".") -__prometheus_version__as_int__ = ( - (100 * int(prometheus_version__split[0])) - + (10 * int(prometheus_version__split[1])) - + (1 * int(prometheus_version__split[2])) -) -try: - bt_promo_info = Info( - "bittensor_info", "Information about the installed bittensor package." - ) - bt_promo_info.info( - { - "__version__": str(__version__), - "__version_as_int__": str(__version_as_int__), - "__vocab_size__": str(__vocab_size__), - "__network_dim__": str(__network_dim__), - "__blocktime__": str(__blocktime__), - "__prometheus_version__": str(__prometheus_version__), - "__prometheus_version__as_int__": str(__prometheus_version__as_int__), - } - ) -except ValueError: - # This can silently fail if we import bittensor twice in the same process. - # We simply pass over this error. - pass - - -# ---- Config ---- -from bittensor_config import config as config - -# ---- LOGGING ---- -# Duplicate import for ease of use. -from bittensor._logging import logging as logging -from bittensor._logging import logging as logger - -# ---- Protos ---- -import bittensor._proto.bittensor_pb2 as proto -import bittensor._proto.bittensor_pb2_grpc as grpc - -# ---- Utils ---- -from bittensor.utils import unbiased_topk as unbiased_topk -from bittensor.utils.tokenizer_utils import topk_token_phrases -from bittensor.utils.tokenizer_utils import compact_topk_token_phrases -from bittensor.utils.tokenizer_utils import unravel_topk_token_phrases -from bittensor.utils.tokenizer_utils import prep_tokenizer -from bittensor._cli.commands import utils as cli_utils - -# ---- Factories ----- -from bittensor.utils.balance import Balance as Balance -from bittensor._cli import cli as cli -from bittensor._axon import axon as axon -from bittensor._axon import axon_info as axon_info -from bittensor_wallet import wallet as wallet -from bittensor_wallet import keyfile as keyfile -from bittensor._metagraph import metagraph as metagraph -from bittensor._prometheus import prometheus as prometheus -from bittensor._subtensor import subtensor as subtensor -from bittensor._tokenizer import tokenizer as tokenizer -from bittensor._serializer import serializer as serializer -from bittensor._dataset import dataset as dataset -from bittensor._threadpool import prioritythreadpool as prioritythreadpool -from bittensor._blacklist import blacklist as blacklist -from bittensor._priority import priority as priority - -# ---- Classes ----- -from bittensor._cli.cli_impl import CLI as CLI -from bittensor_config.config_impl import Config as Config -from bittensor._subtensor.chain_data import DelegateInfo as DelegateInfo -from bittensor_wallet import Wallet as Wallet -from bittensor_wallet import Keyfile as Keyfile -from bittensor_wallet import Keypair as Keypair -from bittensor._subtensor.chain_data import NeuronInfo as NeuronInfo -from bittensor._subtensor.chain_data import NeuronInfoLite as NeuronInfoLite -from bittensor._subtensor.chain_data import PrometheusInfo as PrometheusInfo -from bittensor._subtensor.chain_data import ProposalCallData as ProposalCallData -from bittensor._subtensor.chain_data import ProposalVoteData as ProposalVoteData -from bittensor._subtensor.subtensor_impl import Subtensor as Subtensor -from bittensor._serializer.serializer_impl import Serializer as Serializer -from bittensor._subtensor.chain_data import SubnetInfo as SubnetInfo -from bittensor._dataset.dataset_impl import Dataset as Dataset -from bittensor._threadpool.priority_thread_pool_impl import ( - PriorityThreadPoolExecutor as PriorityThreadPoolExecutor, -) -from bittensor._ipfs.ipfs_impl import Ipfs as Ipfs - -# ---- Errors and Exceptions ----- -from bittensor_wallet import KeyFileError as KeyFileError - -from bittensor._proto.bittensor_pb2 import ForwardTextPromptingRequest -from bittensor._proto.bittensor_pb2 import ForwardTextPromptingResponse -from bittensor._proto.bittensor_pb2 import BackwardTextPromptingRequest -from bittensor._proto.bittensor_pb2 import BackwardTextPromptingResponse - -# ---- Synapses ----- -from bittensor._synapse.synapse import Synapse -from bittensor._synapse.synapse import SynapseCall -from bittensor._synapse.text_prompting.synapse import TextPromptingSynapse - -# ---- Dendrites ----- -from bittensor._dendrite.dendrite import Dendrite -from bittensor._dendrite.dendrite import DendriteCall -from bittensor._dendrite.text_prompting.dendrite import ( - TextPromptingDendrite as text_prompting, -) -from bittensor._dendrite.text_prompting.dendrite_pool import ( - TextPromptingDendritePool as text_prompting_pool, -) - -# ---- Base Miners ----- -from bittensor._neuron.base_miner_neuron import BaseMinerNeuron -from bittensor._neuron.base_prompting_miner import BasePromptingMiner -from bittensor._neuron.base_huggingface_miner import HuggingFaceMiner - -# DEFAULTS -defaults = Config() -defaults.netuid = 1 -subtensor.add_defaults(defaults) -axon.add_defaults(defaults) -prioritythreadpool.add_defaults(defaults) -prometheus.add_defaults(defaults) -wallet.add_defaults(defaults, prefix="wallet") -dataset.add_defaults(defaults) -logging.add_defaults(defaults) - - -# Logging helpers. -def trace(): - logging.set_trace(True) - - -def debug(): - logging.set_debug(True) - - -default_prompt = """ -You are Chattensor. -Chattensor is a research project by Opentensor Cortex. -Chattensor is designed to be able to assist with a wide range of tasks, from answering simple questions to providing in-depth explanations and discussions on a wide range of topics. As a language model, Chattensor is able to generate human-like text based on the input it receives, allowing it to engage in natural-sounding conversations and provide responses that are coherent and relevant to the topic at hand. -""" - -default_prompting_validator_key = "5F4tQyWrhfGVcNhoqeiNsR6KjD4wMZ2kfhLj4oHYuyHbZAc3" - -__context_prompting_llm = None - - -def prompt( - content: Union[str, List[str], List[Dict[str, str]]], - wallet_name: str = "default", - hotkey: str = default_prompting_validator_key, - subtensor_: Optional["Subtensor"] = None, - axon_: Optional["axon_info"] = None, - return_all: bool = False, -) -> str: - global __context_prompting_llm - if __context_prompting_llm == None: - __context_prompting_llm = prompting( - wallet_name=wallet_name, - hotkey=hotkey, - subtensor_=subtensor_, - axon_=axon_, - ) - return __context_prompting_llm(content=content, return_all=return_all) - - -class prompting(torch.nn.Module): - _axon: "axon_info" - _dendrite: "Dendrite" - _subtensor: "Subtensor" - _hotkey: str - _keypair: "Keypair" - - def __init__( - self, - wallet_name: str = "default", - hotkey: str = default_prompting_validator_key, - subtensor_: Optional["Subtensor"] = None, - axon_: Optional["axon_info"] = None, - use_coldkey: bool = False, - ): - super(prompting, self).__init__() - self._hotkey = hotkey - self._subtensor = subtensor() if subtensor_ is None else subtensor_ - if use_coldkey: - self._keypair = wallet(name=wallet_name).create_if_non_existent().coldkey - else: - self._keypair = wallet(name=wallet_name).create_if_non_existent().hotkey - - if axon_ is not None: - self._axon = axon_ - else: - self._metagraph = metagraph(1) - self._axon = self._metagraph.axons[ - self._metagraph.hotkeys.index(self._hotkey) - ] - self._dendrite = text_prompting(keypair=self._keypair, axon=self._axon) - - @staticmethod - def format_content( - content: Union[str, List[str], List[Dict[str, str]]] - ) -> Tuple[List[str], List[str]]: - if isinstance(content, str): - return ["system", "user"], [default_prompt, content] - elif isinstance(content, list): - if isinstance(content[0], str): - return ["user" for _ in content], content - elif isinstance(content[0], dict): - return [dictitem[list(dictitem.keys())[0]] for dictitem in content], [ - dictitem[list(dictitem.keys())[1]] for dictitem in content - ] - else: - raise ValueError("content has invalid type {}".format(type(content))) - else: - raise ValueError("content has invalid type {}".format(type(content))) - - def forward( - self, - content: Union[str, List[str], List[Dict[str, str]]], - timeout: float = 24, - return_call: bool = False, - return_all: bool = False, - ) -> Union[str, List[str]]: - roles, messages = self.format_content(content) - if not return_all: - return self._dendrite.forward( - roles=roles, messages=messages, timeout=timeout - ).completion - else: - return self._dendrite.multi_forward( - roles=roles, messages=messages, timeout=timeout - ).multi_completions - - async def async_forward( - self, - content: Union[str, List[str], List[Dict[str, str]]], - timeout: float = 24, - return_all: bool = False, - ) -> Union[str, List[str]]: - roles, messages = self.format_content(content) - if not return_all: - return await self._dendrite.async_forward( - roles=roles, messages=messages, timeout=timeout - ).completion - else: - return self._dendrite.async_multi_forward( - roles=roles, messages=messages, timeout=timeout - ).multi_completions - - -class BittensorLLM(LLM): - """Wrapper around Bittensor Prompting Subnetwork. - This Python file implements the BittensorLLM class, a wrapper around the Bittensor Prompting Subnetwork for easy integration into language models. The class provides a query method to receive responses from the subnetwork for a given user message and an implementation of the _call method to return the best response. The class can be initialized with various parameters such as the wallet name and chain endpoint. - - Example: - .. code-block:: python - - from bittensor import BittensorLLM - btllm = BittensorLLM(wallet_name="default") - """ - - wallet_name: str = "default" - hotkey: str = default_prompting_validator_key - llm: prompting = None - - def __init__( - self, - subtensor_: Optional["Subtensor"] = None, - axon_: Optional["axon_info"] = None, - **data - ): - super().__init__(**data) - self.llm = prompting( - wallet_name=self.wallet_name, - hotkey=self.hotkey, - subtensor_=subtensor_, - axon_=axon_, - ) - - @property - def _identifying_params(self) -> Mapping[str, Any]: - """Get the identifying parameters.""" - return {"wallet_name": self.wallet_name, "hotkey_name": self.hotkey} - - @property - def _llm_type(self) -> str: - return "BittensorLLM" - - def _call(self, prompt: str, stop: Optional[List[str]] = None) -> str: - """Call the LLM with the given prompt and stop tokens.""" - return self.llm(prompt) +from .errors import * + +from substrateinterface import Keypair as Keypair +from .config import * +from .keyfile import * +from .wallet import * + +from .utils import * +from .utils.balance import Balance as Balance +from .chain_data import * +from .subtensor import subtensor as subtensor +from .cli import cli as cli, COMMANDS as ALL_COMMANDS +from .logging import logging as logging +from .metagraph import metagraph as metagraph +from .threadpool import PriorityThreadPoolExecutor as PriorityThreadPoolExecutor + +from .synapse import * +from .stream import * +from .tensor import * +from .axon import axon as axon +from .dendrite import dendrite as dendrite + +from .mock.keyfile_mock import MockKeyfile as MockKeyfile +from .mock.subtensor_mock import MockSubtensor as MockSubtensor +from .mock.wallet_mock import MockWallet as MockWallet + +configs = [ + axon.config(), + subtensor.config(), + PriorityThreadPoolExecutor.config(), + wallet.config(), + logging.config(), +] +defaults = config.merge_all(configs) diff --git a/bittensor/_axon/__init__.py b/bittensor/_axon/__init__.py deleted file mode 100644 index de1ccc948e..0000000000 --- a/bittensor/_axon/__init__.py +++ /dev/null @@ -1,450 +0,0 @@ -""" Create and init Axon, whcih services Forward and Backward requests from other neurons. -""" -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao -# Copyright © 2022 Opentensor Foundation - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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 os -import json -import grpc -import copy -import torch -import argparse -import bittensor - -from concurrent import futures -from dataclasses import dataclass -from substrateinterface import Keypair -import bittensor.utils.networking as net -from typing import Dict, Optional, Tuple - - -class axon: - """Axon object for serving synapse receptors.""" - - def info(self) -> "axon_info": - """Returns the axon info object associate with this axon.""" - return axon_info( - version=bittensor.__version_as_int__, - ip=self.external_ip, - ip_type=4, - port=self.external_port, - hotkey=self.wallet.hotkey.ss58_address, - coldkey=self.wallet.coldkeypub.ss58_address, - protocol=4, - placeholder1=0, - placeholder2=0, - ) - - def __init__( - self, - wallet: "bittensor.Wallet", - metagraph: Optional["bittensor.Metagraph"] = None, - config: Optional["bittensor.config"] = None, - port: Optional[int] = None, - ip: Optional[str] = None, - external_ip: Optional[str] = None, - external_port: Optional[int] = None, - max_workers: Optional[int] = None, - server: "grpc._server._Server" = None, - maximum_concurrent_rpcs: Optional[int] = None, - ) -> "bittensor.Axon": - r"""Creates a new bittensor.Axon object from passed arguments. - Args: - config (:obj:`Optional[bittensor.Config]`, `optional`): - bittensor.axon.config() - wallet (:obj:`Optional[bittensor.Wallet]`, `optional`): - bittensor wallet with hotkey and coldkeypub. - port (:type:`Optional[int]`, `optional`): - Binding port. - ip (:type:`Optional[str]`, `optional`): - Binding ip. - external_ip (:type:`Optional[str]`, `optional`): - The external ip of the server to broadcast to the network. - external_port (:type:`Optional[int]`, `optional`): - The external port of the server to broadcast to the network. - max_workers (:type:`Optional[int]`, `optional`): - Used to create the threadpool if not passed, specifies the number of active threads servicing requests. - maximum_concurrent_rpcs (:type:`Optional[int]`, `optional`): - Maximum allowed concurrently processed RPCs. - """ - self.metagraph = metagraph - self.wallet = wallet - - # Build and check config. - if config is None: - config = axon.config() - config = copy.deepcopy(config) - config.axon.port = port if port is not None else config.axon.port - config.axon.ip = ip if ip is not None else config.axon.ip - config.axon.external_ip = ( - external_ip if external_ip is not None else config.axon.external_ip - ) - config.axon.external_port = ( - external_port if external_port is not None else config.axon.external_port - ) - config.axon.max_workers = ( - max_workers if max_workers is not None else config.axon.max_workers - ) - config.axon.maximum_concurrent_rpcs = ( - maximum_concurrent_rpcs - if maximum_concurrent_rpcs is not None - else config.axon.maximum_concurrent_rpcs - ) - axon.check_config(config) - self.config = config - - # Build axon objects. - self.ip = self.config.axon.ip - self.port = self.config.axon.port - self.external_ip = ( - self.config.axon.external_ip - if self.config.axon.external_ip != None - else bittensor.utils.networking.get_external_ip() - ) - self.external_port = ( - self.config.axon.external_port - if self.config.axon.external_port != None - else self.config.axon.port - ) - self.full_address = str(self.config.axon.ip) + ":" + str(self.config.axon.port) - self.started = False - - # Build priority thread pool - self.priority_threadpool = bittensor.prioritythreadpool(config=self.config.axon) - - # Build interceptor. - self.receiver_hotkey = self.wallet.hotkey.ss58_address - self.auth_interceptor = AuthInterceptor(receiver_hotkey=self.receiver_hotkey) - - # Build grpc server - if server is None: - self.thread_pool = futures.ThreadPoolExecutor( - max_workers=self.config.axon.max_workers - ) - self.server = grpc.server( - self.thread_pool, - interceptors=(self.auth_interceptor,), - maximum_concurrent_rpcs=self.config.axon.maximum_concurrent_rpcs, - options=[ - ("grpc.keepalive_time_ms", 100000), - ("grpc.keepalive_timeout_ms", 500000), - ], - ) - self.server.add_insecure_port(self.full_address) - else: - self.server = server - self.thread_pool = server._state.thread_pool - self.server.add_insecure_port(self.full_address) - - @classmethod - def config(cls) -> "bittensor.Config": - """Get config from the argument parser - Return: bittensor.config object - """ - parser = argparse.ArgumentParser() - axon.add_args(parser) - return bittensor.config(parser) - - @classmethod - def help(cls): - """Print help to stdout""" - parser = argparse.ArgumentParser() - cls.add_args(parser) - print(cls.__new__.__doc__) - parser.print_help() - - @classmethod - def add_args(cls, parser: argparse.ArgumentParser, prefix: str = None): - """Accept specific arguments from parser""" - prefix_str = "" if prefix is None else prefix + "." - if prefix is not None: - if bittensor.defaults.get(prefix, d=None) == None: - setattr(bittensor.defaults, prefix, bittensor.Config()) - getattr(bittensor.defaults, prefix).axon = bittensor.defaults.axon - - bittensor.prioritythreadpool.add_args(parser, prefix=prefix_str + "axon") - try: - parser.add_argument( - "--" + prefix_str + "axon.port", - type=int, - help="""The local port this axon endpoint is bound to. i.e. 8091""", - default=bittensor.defaults.axon.port, - ) - parser.add_argument( - "--" + prefix_str + "axon.ip", - type=str, - help="""The local ip this axon binds to. ie. [::]""", - default=bittensor.defaults.axon.ip, - ) - parser.add_argument( - "--" + prefix_str + "axon.external_port", - type=int, - required=False, - help="""The public port this axon broadcasts to the network. i.e. 8091""", - default=bittensor.defaults.axon.external_port, - ) - parser.add_argument( - "--" + prefix_str + "axon.external_ip", - type=str, - required=False, - help="""The external ip this axon broadcasts to the network to. ie. [::]""", - default=bittensor.defaults.axon.external_ip, - ) - parser.add_argument( - "--" + prefix_str + "axon.max_workers", - type=int, - help="""The maximum number connection handler threads working simultaneously on this endpoint. - The grpc server distributes new worker threads to service requests up to this number.""", - default=bittensor.defaults.axon.max_workers, - ) - parser.add_argument( - "--" + prefix_str + "axon.maximum_concurrent_rpcs", - type=int, - help="""Maximum number of allowed active connections""", - default=bittensor.defaults.axon.maximum_concurrent_rpcs, - ) - except argparse.ArgumentError: - # re-parsing arguments. - pass - - @classmethod - def add_defaults(cls, defaults): - """Adds parser defaults to object from enviroment variables.""" - defaults.axon = bittensor.Config() - defaults.axon.port = ( - os.getenv("BT_AXON_PORT") if os.getenv("BT_AXON_PORT") is not None else 8091 - ) - defaults.axon.ip = ( - os.getenv("BT_AXON_IP") if os.getenv("BT_AXON_IP") is not None else "[::]" - ) - defaults.axon.external_port = ( - os.getenv("BT_AXON_EXTERNAL_PORT") - if os.getenv("BT_AXON_EXTERNAL_PORT") is not None - else None - ) - defaults.axon.external_ip = ( - os.getenv("BT_AXON_EXTERNAL_IP") - if os.getenv("BT_AXON_EXTERNAL_IP") is not None - else None - ) - defaults.axon.max_workers = ( - os.getenv("BT_AXON_MAX_WORERS") - if os.getenv("BT_AXON_MAX_WORERS") is not None - else 10 - ) - defaults.axon.maximum_concurrent_rpcs = ( - os.getenv("BT_AXON_MAXIMUM_CONCURRENT_RPCS") - if os.getenv("BT_AXON_MAXIMUM_CONCURRENT_RPCS") is not None - else 400 - ) - - @classmethod - def check_config(cls, config: "bittensor.Config"): - """Check config for axon port and wallet""" - assert ( - config.axon.port > 1024 and config.axon.port < 65535 - ), "port must be in range [1024, 65535]" - assert config.axon.external_port is None or ( - config.axon.external_port > 1024 and config.axon.external_port < 65535 - ), "external port must be in range [1024, 65535]" - - def __str__(self) -> str: - return "Axon({}, {}, {}, {})".format( - self.ip, - self.port, - self.wallet.hotkey.ss58_address, - "started" if self.started else "stopped", - ) - - def __repr__(self) -> str: - return self.__str__() - - def __del__(self): - r"""Called when this axon is deleted, ensures background threads shut down properly.""" - self.stop() - - def start(self) -> "bittensor.axon": - r"""Starts the standalone axon GRPC server thread.""" - if self.server is not None: - self.server.stop(grace=1) - self.server.start() - self.started = True - return self - - def stop(self) -> "bittensor.axon": - r"""Stop the axon grpc server.""" - if hasattr(self, "server") and self.server is not None: - self.server.stop(grace=1) - self.started = False - - -class AuthInterceptor(grpc.ServerInterceptor): - """Creates a new server interceptor that authenticates incoming messages from passed arguments.""" - - def __init__(self, receiver_hotkey: str): - r"""Creates a new server interceptor that authenticates incoming messages from passed arguments. - Args: - receiver_hotkey(str): - the SS58 address of the hotkey which should be targeted by RPCs - """ - super().__init__() - self.nonces = {} - self.receiver_hotkey = receiver_hotkey - - def parse_signature_v2(self, signature: str) -> Optional[Tuple[int, str, str, str]]: - r"""Attempts to parse a signature using the v2 format""" - parts = signature.split(".") - if len(parts) != 4: - return None - try: - nonce = int(parts[0]) - except ValueError: - return None - sender_hotkey = parts[1] - signature = parts[2] - receptor_uuid = parts[3] - return (nonce, sender_hotkey, signature, receptor_uuid) - - def parse_signature(self, metadata: Dict[str, str]) -> Tuple[int, str, str, str]: - r"""Attempts to parse a signature from the metadata""" - signature = metadata.get("bittensor-signature") - version = metadata.get("bittensor-version") - if signature is None: - raise Exception("Request signature missing") - if int(version) < 510: - raise Exception("Incorrect Version") - parts = self.parse_signature_v2(signature) - if parts is not None: - return parts - raise Exception("Unknown signature format") - - def check_signature( - self, nonce: int, sender_hotkey: str, signature: str, receptor_uuid: str - ): - r"""verification of signature in metadata. Uses the pubkey and nonce""" - keypair = Keypair(ss58_address=sender_hotkey) - # Build the expected message which was used to build the signature. - message = f"{nonce}.{sender_hotkey}.{self.receiver_hotkey}.{receptor_uuid}" - - # Build the key which uniquely identifies the endpoint that has signed - # the message. - endpoint_key = f"{sender_hotkey}:{receptor_uuid}" - - if endpoint_key in self.nonces.keys(): - previous_nonce = self.nonces[endpoint_key] - # Nonces must be strictly monotonic over time. - if nonce <= previous_nonce: - raise Exception("Nonce is too small") - - if not keypair.verify(message, signature): - raise Exception("Signature mismatch") - self.nonces[endpoint_key] = nonce - - def intercept_service(self, continuation, handler_call_details): - r"""Authentication between bittensor nodes. Intercepts messages and checks them""" - metadata = dict(handler_call_details.invocation_metadata) - - try: - (nonce, sender_hotkey, signature, receptor_uuid) = self.parse_signature( - metadata - ) - - # signature checking - self.check_signature(nonce, sender_hotkey, signature, receptor_uuid) - - return continuation(handler_call_details) - - except Exception as e: - message = str(e) - abort = lambda _, ctx: ctx.abort(grpc.StatusCode.UNAUTHENTICATED, message) - return grpc.unary_unary_rpc_method_handler(abort) - - -METADATA_BUFFER_SIZE = 250 - - -@dataclass -class axon_info: - version: int - ip: str - port: int - ip_type: int - hotkey: str - coldkey: str - protocol: int = (4,) - placeholder1: int = (0,) - placeholder2: int = (0,) - - @property - def is_serving(self) -> bool: - """True if the endpoint is serving.""" - if self.ip == "0.0.0.0": - return False - else: - return True - - def ip_str(self) -> str: - """Return the whole ip as string""" - return net.ip__str__(self.ip_type, self.ip, self.port) - - def __eq__(self, other: "axon_info"): - if other == None: - return False - if ( - self.version == other.version - and self.ip == other.ip - and self.port == other.port - and self.ip_type == other.ip_type - and self.coldkey == other.coldkey - and self.hotkey == other.hotkey - ): - return True - else: - return False - - def __str__(self): - return "axon_info( {}, {}, {}, {} )".format( - str(self.ip_str()), str(self.hotkey), str(self.coldkey), self.version - ) - - def __repr__(self): - return self.__str__() - - @classmethod - def from_neuron_info(cls, neuron_info: dict) -> "axon_info": - """Converts a dictionary to an axon_info object.""" - return cls( - version=neuron_info["axon_info"]["version"], - ip=bittensor.utils.networking.int_to_ip( - int(neuron_info["axon_info"]["ip"]) - ), - port=neuron_info["axon_info"]["port"], - ip_type=neuron_info["axon_info"]["ip_type"], - hotkey=neuron_info["hotkey"], - 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__) - - @classmethod - def from_parameter_dict( - cls, parameter_dict: "torch.nn.ParameterDict" - ) -> "axon_info": - r"""Returns an axon_info object from a torch parameter_dict.""" - return cls(**dict(parameter_dict)) diff --git a/bittensor/_blacklist/__init__.py b/bittensor/_blacklist/__init__.py deleted file mode 100644 index 7b0f0481df..0000000000 --- a/bittensor/_blacklist/__init__.py +++ /dev/null @@ -1,122 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao -# Copyright © 2022 Opentensor Foundation - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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 argparse -import bittensor -from typing import Union, Tuple - - -class blacklist: - def __init__(self, config: "bittensor.Config" = None): - self.config = config or blacklist.config() - - @classmethod - def config(cls) -> "bittensor.Config": - parser = argparse.ArgumentParser() - blacklist.add_args(parser) - return bittensor.config(parser) - - @classmethod - def help(cls): - parser = argparse.ArgumentParser() - cls.add_args(parser) - print(cls.__new__.__doc__) - parser.print_help() - - @classmethod - def add_args(cls, parser: argparse.ArgumentParser, prefix: str = None): - prefix_str = "" if prefix is None else prefix + "." - parser.add_argument( - "--" + prefix_str + "blacklist.blacklisted_keys", - type=str, - required=False, - nargs="*", - action="store", - help="List of ss58 addresses which are always disallowed pass through.", - default=[], - ) - parser.add_argument( - "--" + prefix_str + "blacklist.whitelisted_keys", - type=str, - required=False, - nargs="*", - action="store", - help="List of ss58 addresses which are always allowed pass through.", - default=[], - ) - parser.add_argument( - "--" + prefix_str + "blacklist.allow_non_registered", - action="store_true", - help="If True, the miner will allow non-registered hotkeys to pass blacklist.", - default=False, - ) - parser.add_argument( - "--" + prefix_str + "blacklist.min_allowed_stake", - type=float, - help="Minimum stake required to pass blacklist.", - default=0.0, - ) - parser.add_argument( - "--" + prefix_str + "blacklist.vpermit_required", - action="store_true", - help="If True, the miner will require a vpermit to pass blacklist.", - default=False, - ) - - def blacklist( - self, - forward_call: "bittensor.SynapseCall", - metagraph: "bittensor.Metagraph" = None, - ) -> Union[Tuple[bool, str], bool]: - # Check for blacklisted keys which take priority over all other checks. - src_hotkey = forward_call.src_hotkey - if src_hotkey in self.config.blacklist.blacklisted_keys: - return True, "blacklisted key" - - # Check for whitelisted keys which take priority over all remaining checks. - if src_hotkey in self.config.blacklist.whitelisted_keys: - return False, "whitelisted key" - - # Check if pubkey is registered. - is_registered = False - if metagraph is not None: - is_registered = src_hotkey in metagraph.hotkeys - - if not is_registered and not self.config.blacklist.allow_non_registered: - return True, "pubkey not registered" - - # Check for stake amount. - if is_registered and self.config.blacklist.min_allowed_stake > 0.0: - uid = metagraph.hotkeys.index(src_hotkey) - stake = metagraph.S[uid].item() - if stake < self.config.blacklist.min_allowed_stake: - return True, "pubkey stake below min_allowed_stake" - - # Check for vpermit. - if ( - metagraph is not None - and self.config.blacklist.vpermit_required - and is_registered - ): - uid = metagraph.hotkeys.index(src_hotkey) - # Return False (pass) if there is a permit, and True (fail) if there isn't. - if metagraph.neurons[uid].validator_permit: - return False, "has vpermit" - return True, "no vpermit" - - # All checks passed. - return False, "passed blacklist" diff --git a/bittensor/_cli/__init__.py b/bittensor/_cli/__init__.py deleted file mode 100644 index 4c099f9345..0000000000 --- a/bittensor/_cli/__init__.py +++ /dev/null @@ -1,175 +0,0 @@ -""" -Create and init the CLI class, which handles the coldkey, hotkey and money transfer -""" -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao -# Copyright © 2022 Opentensor Foundation - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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 sys -import argparse -import bittensor -from . import cli_impl -from .commands import * -from typing import List, Optional - -console = bittensor.__console__ - -# Turn off rich console locals trace. -from rich.traceback import install - -install(show_locals=False) - - -class cli: - """ - Create and init the CLI class, which handles the coldkey, hotkey and tao transfer - """ - - def __new__( - cls, - config: Optional["bittensor.Config"] = None, - args: Optional[List[str]] = None, - ) -> "bittensor.CLI": - r"""Creates a new bittensor.cli from passed arguments. - Args: - config (:obj:`bittensor.Config`, `optional`): - bittensor.cli.config() - args (`List[str]`, `optional`): - The arguments to parse from the command line. - """ - if config == None: - config = cli.config(args) - cli.check_config(config) - - return cli_impl.CLI(config=config) - - @staticmethod - def __create_parser__() -> "argparse.ArgumentParser": - """Creates the argument parser for the bittensor cli.""" - parser = argparse.ArgumentParser( - description=f"bittensor cli v{bittensor.__version__}", - usage="btcli ", - add_help=True, - ) - - cmd_parsers = parser.add_subparsers(dest="command") - ListCommand.add_args(cmd_parsers) - StakeCommand.add_args(cmd_parsers) - UpdateCommand.add_args(cmd_parsers) - InspectCommand.add_args(cmd_parsers) - UnStakeCommand.add_args(cmd_parsers) - OverviewCommand.add_args(cmd_parsers) - RegisterCommand.add_args(cmd_parsers) - TransferCommand.add_args(cmd_parsers) - NominateCommand.add_args(cmd_parsers) - NewHotkeyCommand.add_args(cmd_parsers) - MetagraphCommand.add_args(cmd_parsers) - NewColdkeyCommand.add_args(cmd_parsers) - MyDelegatesCommand.add_args(cmd_parsers) - ListSubnetsCommand.add_args(cmd_parsers) - RegenHotkeyCommand.add_args(cmd_parsers) - RegenColdkeyCommand.add_args(cmd_parsers) - DelegateStakeCommand.add_args(cmd_parsers) - DelegateUnstakeCommand.add_args(cmd_parsers) - ListDelegatesCommand.add_args(cmd_parsers) - RegenColdkeypubCommand.add_args(cmd_parsers) - RecycleRegisterCommand.add_args(cmd_parsers) - SenateCommand.add_args(cmd_parsers) - ProposalsCommand.add_args(cmd_parsers) - ShowVotesCommand.add_args(cmd_parsers) - SenateRegisterCommand.add_args(cmd_parsers) - SenateLeaveCommand.add_args(cmd_parsers) - VoteCommand.add_args(cmd_parsers) - - return parser - - @staticmethod - def config(args: List[str]) -> "bittensor.config": - """From the argument parser, add config to bittensor.executor and local config - Return: bittensor.config object - """ - parser = cli.__create_parser__() - - # If no arguments are passed, print help text. - if len(args) == 0: - parser.print_help() - sys.exit() - - return bittensor.config(parser, args=args) - - @staticmethod - def check_config(config: "bittensor.Config"): - """Check if the essential config exist under different command""" - if config.command == "transfer": - TransferCommand.check_config(config) - elif config.command == "register": - RegisterCommand.check_config(config) - elif config.command == "unstake": - UnStakeCommand.check_config(config) - elif config.command == "stake": - StakeCommand.check_config(config) - elif config.command == "overview": - OverviewCommand.check_config(config) - elif config.command == "new_coldkey": - NewColdkeyCommand.check_config(config) - elif config.command == "new_hotkey": - NewHotkeyCommand.check_config(config) - elif config.command == "regen_coldkey": - RegenColdkeyCommand.check_config(config) - elif config.command == "regen_coldkeypub": - RegenColdkeypubCommand.check_config(config) - elif config.command == "regen_hotkey": - RegenHotkeyCommand.check_config(config) - elif config.command == "metagraph": - MetagraphCommand.check_config(config) - elif config.command == "list": - ListCommand.check_config(config) - elif config.command == "inspect": - InspectCommand.check_config(config) - elif config.command == "update": - UpdateCommand.check_config(config) - elif config.command == "nominate": - NominateCommand.check_config(config) - elif config.command == "list_delegates": - ListDelegatesCommand.check_config(config) - elif config.command == "list_subnets": - ListSubnetsCommand.check_config(config) - elif config.command == "delegate": - DelegateStakeCommand.check_config(config) - elif config.command == "undelegate": - DelegateUnstakeCommand.check_config(config) - elif config.command == "my_delegates": - MyDelegatesCommand.check_config(config) - elif config.command == "recycle_register": - RecycleRegisterCommand.check_config(config) - elif config.command == "senate": - SenateCommand.check_config(config) - elif config.command == "proposals": - ProposalsCommand.check_config(config) - elif config.command == "proposal_votes": - ShowVotesCommand.check_config(config) - elif config.command == "senate_register": - SenateRegisterCommand.check_config(config) - elif config.command == "senate_leave": - SenateLeaveCommand.check_config(config) - elif config.command == "senate_vote": - VoteCommand.check_config(config) - else: - console.print( - ":cross_mark:[red]Unknown command: {}[/red]".format(config.command) - ) - sys.exit() diff --git a/bittensor/_cli/cli_impl.py b/bittensor/_cli/cli_impl.py deleted file mode 100644 index b2e68c06d8..0000000000 --- a/bittensor/_cli/cli_impl.py +++ /dev/null @@ -1,99 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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 bittensor -from .commands import * - - -class CLI: - """ - Implementation of the CLI class, which handles the coldkey, hotkey and money transfer - """ - - def __init__(self, config: "bittensor.Config"): - r"""Initialized a bittensor.CLI object. - Args: - config (:obj:`bittensor.Config`, `required`): - bittensor.cli.config() - """ - # (d)efaults to True if config.no_version_checking is not set. - if not config.get("no_version_checking", d=True): - try: - bittensor.utils.version_checking() - except: - raise RuntimeError( - "To avoid internet based version checking pass --no_version_checking while running the CLI." - ) - self.config = config - - def run(self): - """Execute the command from config""" - if self.config.command == "transfer": - TransferCommand.run(self) - elif self.config.command == "register": - RegisterCommand.run(self) - elif self.config.command == "unstake": - UnStakeCommand.run(self) - elif self.config.command == "stake": - StakeCommand.run(self) - elif self.config.command == "overview": - OverviewCommand.run(self) - elif self.config.command == "list": - ListCommand.run(self) - elif self.config.command == "new_coldkey": - NewColdkeyCommand.run(self) - elif self.config.command == "new_hotkey": - NewHotkeyCommand.run(self) - elif self.config.command == "regen_coldkey": - RegenColdkeyCommand.run(self) - elif self.config.command == "regen_coldkeypub": - RegenColdkeypubCommand.run(self) - elif self.config.command == "regen_hotkey": - RegenHotkeyCommand.run(self) - elif self.config.command == "metagraph": - MetagraphCommand.run(self) - elif self.config.command == "inspect": - InspectCommand.run(self) - elif self.config.command == "update": - UpdateCommand.run(self) - elif self.config.command == "nominate": - NominateCommand.run(self) - elif self.config.command == "delegate": - DelegateStakeCommand.run(self) - elif self.config.command == "undelegate": - DelegateUnstakeCommand.run(self) - elif self.config.command == "my_delegates": - MyDelegatesCommand.run(self) - elif self.config.command == "list_delegates": - ListDelegatesCommand.run(self) - elif self.config.command == "list_subnets": - ListSubnetsCommand.run(self) - elif self.config.command == "recycle_register": - RecycleRegisterCommand.run(self) - elif self.config.command == "senate": - SenateCommand.run(self) - elif self.config.command == "proposals": - ProposalsCommand.run(self) - elif self.config.command == "proposal_votes": - ShowVotesCommand.run(self) - elif self.config.command == "senate_register": - SenateRegisterCommand.run(self) - elif self.config.command == "senate_leave": - SenateLeaveCommand.run(self) - elif self.config.command == "senate_vote": - VoteCommand.run(self) diff --git a/bittensor/_cli/commands/__init__.py b/bittensor/_cli/commands/__init__.py deleted file mode 100644 index 4d2bb63f13..0000000000 --- a/bittensor/_cli/commands/__init__.py +++ /dev/null @@ -1,31 +0,0 @@ -from .stake import StakeCommand -from .unstake import UnStakeCommand -from .overview import OverviewCommand -from .register import RegisterCommand, RecycleRegisterCommand -from .delegates import ( - NominateCommand, - ListDelegatesCommand, - DelegateStakeCommand, - DelegateUnstakeCommand, - MyDelegatesCommand, -) -from .wallets import ( - NewColdkeyCommand, - NewHotkeyCommand, - RegenColdkeyCommand, - RegenColdkeypubCommand, - RegenHotkeyCommand, -) -from .transfer import TransferCommand -from .inspect import InspectCommand -from .metagraph import MetagraphCommand -from .list import ListCommand -from .misc import UpdateCommand, ListSubnetsCommand -from .senate import ( - SenateCommand, - ProposalsCommand, - ShowVotesCommand, - SenateRegisterCommand, - SenateLeaveCommand, - VoteCommand, -) diff --git a/bittensor/_cli/commands/register.py b/bittensor/_cli/commands/register.py deleted file mode 100644 index d1bc4f8881..0000000000 --- a/bittensor/_cli/commands/register.py +++ /dev/null @@ -1,196 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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 sys -import argparse -import bittensor -from rich.prompt import Prompt, Confirm -from .utils import check_netuid_set, check_for_cuda_reg_config - -console = bittensor.__console__ - - -class RegisterCommand: - @staticmethod - def run(cli): - r"""Register neuron.""" - wallet = bittensor.wallet(config=cli.config) - subtensor = bittensor.subtensor(config=cli.config) - - # Verify subnet exists - if not subtensor.subnet_exists(netuid=cli.config.netuid): - bittensor.__console__.print( - f"[red]Subnet {cli.config.netuid} does not exist[/red]" - ) - sys.exit(1) - - subtensor.register( - wallet=wallet, - netuid=cli.config.netuid, - prompt=not cli.config.no_prompt, - TPB=cli.config.subtensor.register.cuda.get("TPB", None), - update_interval=cli.config.subtensor.register.get("update_interval", None), - num_processes=cli.config.subtensor.register.get("num_processes", None), - cuda=cli.config.subtensor.register.cuda.get( - "use_cuda", bittensor.defaults.subtensor.register.cuda.use_cuda - ), - dev_id=cli.config.subtensor.register.cuda.get("dev_id", None), - output_in_place=cli.config.subtensor.register.get( - "output_in_place", bittensor.defaults.subtensor.register.output_in_place - ), - log_verbose=cli.config.subtensor.register.get( - "verbose", bittensor.defaults.subtensor.register.verbose - ), - ) - - @staticmethod - def add_args(parser: argparse.ArgumentParser): - register_parser = parser.add_parser( - "register", help="""Register a wallet to a network.""" - ) - register_parser.add_argument( - "--no_version_checking", - action="store_true", - help="""Set false to stop cli version checking""", - default=False, - ) - register_parser.add_argument( - "--no_prompt", - dest="no_prompt", - action="store_true", - help="""Set true to avoid prompting the user.""", - default=False, - ) - register_parser.add_argument( - "--netuid", - type=int, - help="netuid for subnet to serve this neuron on", - default=argparse.SUPPRESS, - ) - - bittensor.wallet.add_args(register_parser) - bittensor.subtensor.add_args(register_parser) - - @staticmethod - def check_config(config: "bittensor.Config"): - check_netuid_set(config, subtensor=bittensor.subtensor(config=config)) - - if not config.is_set("wallet.name") and not config.no_prompt: - wallet_name = Prompt.ask( - "Enter wallet name", default=bittensor.defaults.wallet.name - ) - config.wallet.name = str(wallet_name) - - if not config.is_set("wallet.hotkey") and not config.no_prompt: - hotkey = Prompt.ask( - "Enter hotkey name", default=bittensor.defaults.wallet.hotkey - ) - config.wallet.hotkey = str(hotkey) - - if not config.no_prompt: - check_for_cuda_reg_config(config) - - -class RecycleRegisterCommand: - @staticmethod - def run(cli): - r"""Register neuron by recycling some TAO.""" - wallet = bittensor.wallet(config=cli.config) - subtensor = bittensor.subtensor(config=cli.config) - - # Verify subnet exists - if not subtensor.subnet_exists(netuid=cli.config.netuid): - bittensor.__console__.print( - f"[red]Subnet {cli.config.netuid} does not exist[/red]" - ) - sys.exit(1) - - # Check current recycle amount - current_recycle = subtensor.burn(netuid=cli.config.netuid) - balance = subtensor.get_balance(address=wallet.coldkeypub.ss58_address) - - # Check balance is sufficient - if balance < current_recycle: - bittensor.__console__.print( - f"[red]Insufficient balance {balance} to register neuron. Current recycle is {current_recycle} TAO[/red]" - ) - sys.exit(1) - - if not cli.config.no_prompt: - if ( - Confirm.ask( - f"Your balance is: [bold green]{balance}[/bold green]\nThe cost to register by recycle is [bold red]{current_recycle}[/bold red]\nDo you want to continue?", - default=False, - ) - == False - ): - sys.exit(1) - - subtensor.burned_register( - wallet=wallet, netuid=cli.config.netuid, prompt=not cli.config.no_prompt - ) - - @staticmethod - def add_args(parser: argparse.ArgumentParser): - recycle_register_parser = parser.add_parser( - "recycle_register", help="""Register a wallet to a network.""" - ) - recycle_register_parser.add_argument( - "--no_version_checking", - action="store_true", - help="""Set false to stop cli version checking""", - default=False, - ) - recycle_register_parser.add_argument( - "--no_prompt", - dest="no_prompt", - action="store_true", - help="""Set true to avoid prompting the user.""", - default=False, - ) - recycle_register_parser.add_argument( - "--netuid", - type=int, - help="netuid for subnet to serve this neuron on", - default=argparse.SUPPRESS, - ) - - bittensor.wallet.add_args(recycle_register_parser) - bittensor.subtensor.add_args(recycle_register_parser) - - @staticmethod - def check_config(config: "bittensor.Config"): - if not config.is_set("subtensor.network") and not config.no_prompt: - config.subtensor.network = Prompt.ask( - "Enter subtensor network", - choices=bittensor.__networks__, - default=bittensor.defaults.subtensor.network, - ) - - check_netuid_set(config, subtensor=bittensor.subtensor(config=config)) - - if not config.is_set("wallet.name") and not config.no_prompt: - wallet_name = Prompt.ask( - "Enter wallet name", default=bittensor.defaults.wallet.name - ) - config.wallet.name = str(wallet_name) - - if not config.is_set("wallet.hotkey") and not config.no_prompt: - hotkey = Prompt.ask( - "Enter hotkey name", default=bittensor.defaults.wallet.hotkey - ) - config.wallet.hotkey = str(hotkey) diff --git a/bittensor/_dataset/__init__.py b/bittensor/_dataset/__init__.py deleted file mode 100644 index 3bbb6b10a8..0000000000 --- a/bittensor/_dataset/__init__.py +++ /dev/null @@ -1,291 +0,0 @@ -""" Create and init the GenesisTextDataset class, which handles dataloading from ipfs -""" - -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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 argparse -import os -import copy -from typing import Union -import warnings - -import bittensor -from . import dataset_impl -from . import dataset_mock - - -class dataset: - """Factory class for the GenesisTextDataset class or the mocked GenesisTextDataset - The GenesisTextDataset downloads text data from the bittensor mountain dataset. - The class makes http requests to bittensor's IPFS backend server which contains the full dataset. - By default, the GenesisTextDataset class will return a fully functioning pytorch dataloader. - - Examples:: - >>> dataset = bittensor.dataset(batch_size = 10, block_size=20) - >>> # data.shape[batch_size, block_size] - >>> data = next(dataset) - """ - - def __new__( - cls, - config: "bittensor.config" = None, - block_size: int = None, - batch_size: int = None, - num_workers: int = None, - dataset_names: Union[list, str] = None, - save_dataset: bool = None, - no_tokenizer: bool = None, - num_batches: int = None, - _mock: bool = None, - dataset_name: list = None, # For backwards compatibility - ): - r"""Create and init the GenesisTextDataset class, which handles dataloading from ipfs. - Args: - config (:obj:`bittensor.Config`, `optional`): - bittensor.dataset.config() - block_size (:obj:`int`, `optional`): - Number of text items to pull for each example. - batch_size (:obj:`int`, `optional`): - Batch size. - num_workers (:obj:`int`, `optional`): - Number of workers for data loader. - dataset_names (:obj:`list`,`str`, `optional`): - Which datasets to use (ArXiv, BookCorpus2, Books3, DMMathematics, EnronEmails, EuroParl, - Gutenberg_PG, HackerNews, NIHExPorter, OpenSubtitles, PhilPapers, UbuntuIRC, YoutubeSubtitles)). - save_dataset (:obj:`bool`, `optional`): - Save the downloaded dataset or not. - no_tokenizer (:obj:`bool`, `optional`): - To return non-tokenized text (EXPERIMENTAL, DO NOT USE) - num_batches (:obj:`int`, `optional`): - The number of batches of data to prepare for the dataloader. - _mock (:obj:`bool`, `optional`): - For testing, if true the dataset if filled with fake text data. - """ - if config == None: - config = dataset.config() - config = copy.deepcopy(config) - config.dataset.block_size = ( - block_size if block_size != None else config.dataset.block_size - ) - config.dataset.batch_size = ( - batch_size if batch_size != None else config.dataset.batch_size - ) - config.dataset.num_workers = ( - num_workers if num_workers != None else config.dataset.num_workers - ) - config.dataset.dataset_names = ( - dataset_names if dataset_names != None else config.dataset.dataset_names - ) - config.dataset.save_dataset = ( - save_dataset if save_dataset != None else config.dataset.save_dataset - ) - config.dataset.no_tokenizer = ( - no_tokenizer if no_tokenizer != None else config.dataset.no_tokenizer - ) - config.dataset.num_batches = ( - num_batches if num_batches != None else config.dataset.num_batches - ) - config.dataset._mock = _mock if _mock != None else config.dataset._mock - dataset.check_config(config) - - if dataset_name is not None: - warnings.warn( - "dataset_name as a parameter is deprecated and will be removed in a future release. Use `dataset_names` instead.", - DeprecationWarning, - ) - config.dataset.dataset_names = dataset_name - - if config.dataset._mock: - return dataset_mock.MockGenesisTextDataset( - block_size=config.dataset.block_size, - batch_size=config.dataset.batch_size, - num_workers=config.dataset.num_workers, - dataset_names=config.dataset.dataset_names, - data_dir=config.dataset.data_dir, - save_dataset=config.dataset.save_dataset, - max_datasets=config.dataset.max_datasets, - no_tokenizer=config.dataset.no_tokenizer, - num_batches=config.dataset.num_batches, - ) - else: - return dataset_impl.GenesisTextDataset( - block_size=config.dataset.block_size, - batch_size=config.dataset.batch_size, - num_workers=config.dataset.num_workers, - dataset_names=config.dataset.dataset_names, - data_dir=config.dataset.data_dir, - save_dataset=config.dataset.save_dataset, - max_datasets=config.dataset.max_datasets, - no_tokenizer=config.dataset.no_tokenizer, - num_batches=config.dataset.num_batches, - ) - - @classmethod - def mock(cls): - return dataset(_mock=True, dataset_names=["Books3"]) - - @classmethod - def config(cls) -> "bittensor.Config": - """Get config from the argument parser - Return: bittensor.config object - """ - parser = argparse.ArgumentParser() - dataset.add_args(parser) - return bittensor.config(parser) - - @classmethod - def add_args(cls, parser: argparse.ArgumentParser, prefix: str = None): - """Accept specific arguments from parser""" - prefix_str = "" if prefix == None else prefix + "." - if prefix is not None: - if bittensor.defaults.get(prefix, d=None) == None: - setattr(bittensor.defaults, prefix, bittensor.Config()) - getattr(bittensor.defaults, prefix).dataset = bittensor.defaults.dataset - try: - parser.add_argument( - "--" + prefix_str + "dataset.batch_size", - type=int, - help="Batch size.", - default=bittensor.defaults.dataset.batch_size, - ) - parser.add_argument( - "--" + prefix_str + "dataset.block_size", - type=int, - help="Number of text items to pull for each example..", - default=bittensor.defaults.dataset.block_size, - ) - parser.add_argument( - "--" + prefix_str + "dataset.num_workers", - type=int, - help="Number of workers for data loader.", - default=bittensor.defaults.dataset.num_workers, - ) - parser.add_argument( - "--" + prefix_str + "dataset.dataset_names", - type=str, - required=False, - nargs="*", - action="store", - help="Which datasets to use (ArXiv, BookCorpus2, Books3, DMMathematics, EnronEmails, EuroParl, Gutenberg_PG, HackerNews, NIHExPorter, OpenSubtitles, PhilPapers, UbuntuIRC, YoutubeSubtitles)).", - default=bittensor.defaults.dataset.dataset_names, - ) - parser.add_argument( - "--" + prefix_str + "dataset.data_dir", - type=str, - help="Where to save and load the data.", - default=bittensor.defaults.dataset.data_dir, - ) - parser.add_argument( - "--" + prefix_str + "dataset.save_dataset", - action="store_true", - help="Save the downloaded dataset or not.", - default=bittensor.defaults.dataset.save_dataset, - ) - parser.add_argument( - "--" + prefix_str + "dataset.max_datasets", - type=int, - help="Number of datasets to load", - default=bittensor.defaults.dataset.max_datasets, - ) - parser.add_argument( - "--" + prefix_str + "dataset.no_tokenizer", - action="store_true", - help="To return non-tokenized text (EXPERIMENTAL, DO NOT USE)", - default=False, - ) - parser.add_argument( - "--" + prefix_str + "dataset.num_batches", - type=int, - help="The number of data to download each time(measured by the number of batches).", - default=bittensor.defaults.dataset.num_batches, - ) - parser.add_argument( - "--" + prefix_str + "dataset._mock", - action="store_true", - help="To turn on dataset mocking for testing purposes.", - default=False, - ) - - except argparse.ArgumentError: - # re-parsing arguments. - pass - - @classmethod - def help(cls): - """Print help to stdout""" - parser = argparse.ArgumentParser() - cls.add_args(parser) - print(cls.__new__.__doc__) - parser.print_help() - - @classmethod - def add_defaults(cls, defaults): - """Adds parser defaults to object from enviroment variables.""" - defaults.dataset = bittensor.Config() - defaults.dataset.batch_size = ( - os.getenv("BT_DATASET_BATCH_SIZE") - if os.getenv("BT_DATASET_BATCH_SIZE") != None - else 10 - ) - defaults.dataset.block_size = ( - os.getenv("BT_DATASET_BLOCK_SIZE") - if os.getenv("BT_DATASET_BLOCK_SIZE") != None - else 20 - ) - defaults.dataset.num_workers = ( - os.getenv("BT_DATASET_NUM_WORKERS") - if os.getenv("BT_DATASET_NUM_WORKERS") != None - else 0 - ) - defaults.dataset.dataset_names = ( - os.getenv("BT_DATASET_DATASET_NAME") - if os.getenv("BT_DATASET_DATASET_NAME") != None - else "default" - ) - defaults.dataset.data_dir = ( - os.getenv("BT_DATASET_DATADIR") - if os.getenv("BT_DATASET_DATADIR") != None - else "~/.bittensor/data/" - ) - defaults.dataset.save_dataset = ( - os.getenv("BT_DATASET_SAVE_DATASET") - if os.getenv("BT_DATASET_SAVE_DATASET") != None - else False - ) - defaults.dataset.max_datasets = ( - os.getenv("BT_DATASET_MAX_DATASETS") - if os.getenv("BT_DATASET_MAX_DATASETS") != None - else 3 - ) - defaults.dataset.num_batches = ( - os.getenv("BT_DATASET_NUM_BATCHES") - if os.getenv("BT_DATASET_NUM_BATCHES") != None - else 100 - ) - - @classmethod - def check_config(cls, config: "bittensor.Config"): - """Check config for batch size, block size, corpus size, num_workers and dataset""" - assert config.dataset.batch_size > 0, "Batch size must be larger than 0" - assert config.dataset.block_size > 0, "Block size must be larger than 0" - assert ( - config.dataset.num_workers >= 0 - ), "num_workers must be equal to or larger than 0" - assert isinstance( - config.dataset.save_dataset, bool - ), "save_dataset must be True/False only" diff --git a/bittensor/_dataset/dataset_impl.py b/bittensor/_dataset/dataset_impl.py deleted file mode 100644 index 192f06717e..0000000000 --- a/bittensor/_dataset/dataset_impl.py +++ /dev/null @@ -1,785 +0,0 @@ -""" Implementation for the dataset and GenesisTextDataset class, which handles dataloading from ipfs -""" -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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 concurrent -import json -import os -import random -import time -import warnings -from multiprocessing import cpu_count -from typing import Union - -import requests -import torch -from loguru import logger -from requests.adapters import HTTPAdapter -from requests.packages.urllib3.util.retry import Retry -from torch.utils.data.dataloader import DataLoader - -import bittensor - -from .thread_queue import ThreadQueue - -logger = logger.opt(colors=True) - - -class Dataset: - """Implementation for the dataset class, which handles dataloading from ipfs""" - - def __init__(self): - # Used to retrieve directory contentx - self.dataset_dir = "http://global.ipfs.opentensor.ai/api/v0/cat" - self.text_dir = "http://global.ipfs.opentensor.ai/api/v0/object/get" - self.mountain_hash = "QmSdDg6V9dgpdAFtActs75Qfc36qJtm9y8a7yrQ1rHm7ZX" - # Used when current corpus has been exhausted - self.refresh_corpus = False - - @staticmethod - def requests_retry_session( - retries=1, - backoff_factor=0.5, - status_forcelist=(104, 500, 502, 504), - session=None, - ): - """Creates a retriable session for request calls. This enables - automatic retries and back-off retries should any request calls fail. - - Args: - retries (int, optional): Maximum number of retries. Defaults to 3. - backoff_factor (float, optional): Factor by which to back off if a retry fails. Defaults to 0.3. - status_forcelist (tuple, optional): A set of integer HTTP status codes that we should force a retry on. Defaults to (500, 502, 504). - session ([type], optional): Session for which to set up the retries. Defaults to None. - - Returns: - requests.Session(): A Requests Session object set up for retries and backoff. - """ - session = session or requests.Session() - retry = Retry( - total=retries, - read=retries, - connect=retries, - backoff_factor=backoff_factor, - status_forcelist=status_forcelist, - ) - adapter = HTTPAdapter(max_retries=retry) - session.mount("http://", adapter) - session.mount("https://", adapter) - return session - - def get_ipfs_directory( - self, address: str, file_meta: dict, action: str = "post", timeout: int = 180 - ): - r"""Connects to IPFS gateway and retrieves directory. - Args: - address: (:type:`str`, required): - The target address of the request. - params: (:type:`tuple`, optional): - The arguments of the request. eg. (('arg', dataset_hash),) - action: (:type:`str`, optional): - POST or GET. - timeout: (:type:`int`, optional): - Timeout for getting the server's response. - Returns: - dict: A dictionary of the files inside of the genesis_datasets and their hashes. - """ - session = requests.Session() - session.params.update((("arg", file_meta["Hash"]),)) - - try: - if action == "get": - response = self.requests_retry_session(session=session).get( - address, timeout=timeout - ) - elif action == "post": - response = self.requests_retry_session(session=session).post( - address, timeout=timeout - ) - - except Exception as E: - logger.error(f"Failed to get from IPFS {file_meta['Name']} {E}") - return None - - return response - - def __len__(self): - """Returns length of the dataset that the dataset is processing""" - - def __getitem__(self, idx): - """Returns the next batch from the dataset.""" - - -class GenesisTextDataset(Dataset): - """One kind of dataset that caters for the data from ipfs""" - - def __init__( - self, - block_size, - batch_size, - num_workers, - dataset_names, - data_dir, - save_dataset, - max_datasets, - no_tokenizer, - num_batches, - ): - super().__init__() - self.block_size = block_size - self.batch_size = batch_size - self.num_workers = num_workers - self.tokenizer = bittensor.tokenizer(version=bittensor.__version__) - self.dataset_names = dataset_names - self.data_dir = data_dir - self.save_dataset = save_dataset - self.datafile_size_bound = 262158 - self.max_datasets = max_datasets - self.__infinite_dataset_iterator = None - self.no_tokenizer = no_tokenizer - self.IPFS_fails = 0 - self.backup_dataset_cap_size = 5e7 # set 50MB limit per folder - self.IPFS_fails_max = 10 - self.num_batches = num_batches - - # Ensure dataset_names is formatted correctly - if isinstance(self.dataset_names, str): - self.dataset_names = [self.dataset_names] - - allowed_datasets = bittensor.__datasets__ + ["default"] - for dataset_name in self.dataset_names: - if dataset_name not in allowed_datasets: - self.dataset_names.remove(dataset_name) - warnings.warn( - f"Requested dataset {dataset_name} not in allowed datasets: {allowed_datasets}" - ) - - # Retrieve a random slice of the genesis dataset - self.data = [] - self.data_reserved = [] - - # Used to refresh corpus if we've exhausted the whole dataset - self.refresh_corpus = True - - self.build_hash_table() - - os.makedirs(os.path.expanduser(data_dir), exist_ok=True) - - self.data_queue = ThreadQueue( - producer_target=self.reserve_multiple_data, - producer_arg=(self.num_batches,), - buffer_size=1, - ) - - def __del__(self): - self.close() - - def close(self): - self.data_queue.close() - - def get_folder_size(self, folder): - r"""Get the size (in byte) of a folder inside the data_dir. - Args: - folder (str): - The name of the folder - - Returns: - total_size (int): - The memory size of the folder (in byte). - """ - total_size = 0 - full_path = os.path.expanduser(os.path.join(self.data_dir, folder)) - for dirpath, dirnames, filenames in os.walk(full_path): - for f in filenames: - fp = os.path.join(dirpath, f) - # skip if it is symbolic link - if not os.path.islink(fp): - total_size += os.path.getsize(fp) - - return total_size - - def load_hash(self, file_meta): - r"""Load a hash from disk. - Args: - file_meta (dict of str: int): - Specify the details of the dataset in the format of {'Name': , 'Hash':}. - - Returns: - text (str): - The text in the file. - """ - - full_path = os.path.expanduser( - os.path.join(self.data_dir, file_meta["Folder"], file_meta["Hash"]) - ) - if os.path.exists(full_path): - try: - with open(full_path, mode="r") as f: - text = f.read() - - logger.success( - "Loaded from disk:".ljust(20) - + "{}".format(file_meta["Name"]) - ) - except Exception: - logger.success( - "Could not load from disk:".ljust(20) - + "{}".format(file_meta["Name"]) - ) - pass - - return text - - return None - - def save_hash(self, file_meta, text): - r"""Save a hash to disk. - Args: - file_meta (dict of str: int): - Specify the details of the dataset in the format of {'Name': , 'Hash':}. - text (str): - The string to save to the file. - - Returns: - text (str): - The text in the file. - """ - folder_path = os.path.expanduser( - os.path.join(self.data_dir, file_meta["Folder"]) - ) - full_path = os.path.expanduser( - os.path.join(self.data_dir, file_meta["Folder"], file_meta["Hash"]) - ) - if not os.path.exists(folder_path): - os.makedirs(folder_path) - try: - with open(full_path, mode="w+") as f: - f.write(text) - logger.success( - "Saved:".ljust(20) + "{}".format(file_meta["Name"]) - ) - return True - - except Exception as E: - logger.warning( - "Save failed:".ljust(20) + "{}".format(file_meta["Name"]) - ) - return False - - def get_text(self, file_meta): - r"""Either load a file from disk or download it from IPFS - Args: - file_meta (dict of str: int): - Specify the details of the file in the format of {'Name': , 'Hash':}. - - Return: - text (str): - The text that we get from the file (from disk or IPFS). - """ - text = None - response = self.get_ipfs_directory(self.text_dir, file_meta) - if (response != None) and (response.status_code == 200): - try: - text = json.loads(response.text)["Data"] - except json.decoder.JSONDecodeError: - text = response.text - - self.IPFS_fails = 0 - - if ( - self.save_dataset - and self.dataset_hashes[file_meta["Folder"]]["Size"] - < self.backup_dataset_cap_size - ): - self.save_hash(file_meta, text) - self.dataset_hashes[file_meta["Folder"]]["Size"] += file_meta["Size"] - - else: - logger.warning( - "Failed to get text".ljust(20) - + "{}".format(file_meta["Name"]) - ) - self.IPFS_fails += 1 - - return text - - def get_dataset(self, file_meta): - r"""Either load a dataset, which is a list of hashes, from disk or download it from IPFS - Args: - file_meta (dict of str: int): - Specify the details of the dataset in the format of {'Name': , 'Hash':}. - - Return: - hashes (list): - The hashes from the dataset downloaded from disk or IPFS. - """ - # --- Load text from path - logger.success(f"Getting dataset: {file_meta['Name']}") - - hashes = self.load_hash(file_meta) - - if hashes != None: - hashes = json.loads(hashes) - - # --- If couldnt load from path, download text. - else: - response = self.get_ipfs_directory(self.dataset_dir, file_meta) - if (response != None) and (response.status_code == 200): - self.IPFS_fails = 0 - hashes = response.json() - - # --- Save text if the save_dataset flag is on. - if self.save_dataset: - self.save_hash(file_meta, json.dumps(response.json())) - - else: - self.IPFS_fails += 1 - logger.warning( - "Failed to get dataset".ljust(20) - + "{}".format(file_meta["Name"]) - ) - return None - - if hashes == None: - return None - else: - for h in hashes: - h["Folder"] = file_meta["Name"] - return hashes - - def get_hashes_from_dataset(self): - r"""Getting directories . - Where a directory could be leading to a data file or a directory file. - - Returns: - directories (:type:`list`, `required`) - A list of directory. - directory: Map{ Name: str, Hash: str, Size: int }: - A random directory that lead to a datafile. - """ - - def get_hashes(dataset_meta): - if self.IPFS_fails > self.IPFS_fails_max: - sub_directories = json.loads(self.load_hash(dataset_meta)) - for sub_directory in sub_directories: - sub_directory["Folder"] = dataset_meta["Name"] - else: - sub_directories = self.get_dataset(dataset_meta) - - if sub_directories != None: - return sub_directories - - return [] - - directories = [] - self.IPFS_fails = 0 - - if self.dataset_names == ["default"]: - i = 0 - dataset_hashes = list(self.dataset_hashes.values()) - random.shuffle(dataset_hashes) - - for dataset_hash in dataset_hashes: - dataset_meta = { - "Folder": "mountain", - "Name": dataset_hash["Name"], - "Hash": dataset_hash["Hash"], - } - directories += get_hashes(dataset_meta) - i += 1 - if i >= self.max_datasets: - break - - else: - for key in self.dataset_names: - if key in self.dataset_hashes.keys(): - dataset_meta = { - "Folder": "mountain", - "Name": key, - "Hash": self.dataset_hashes[key]["Hash"], - } - directories += get_hashes(dataset_meta) - - else: - logger.error( - "Incorrect dataset name:".ljust(20) - + " {}.".format(key) - + " Must be one of the following {}".format( - bittensor.__datasets__ - ) - ) - - if len(directories) == 0: - logger.error("Could not get any directory from IPFS or local.") - directories = None - - return directories - - def get_root_text_hash(self, file_meta): - r""" - With recursion, from the given directory, get a directory that leads to a datafile. - - Args: - directory: Map{ Name: str, Hash: str, Size: int }: - The original directory to look up a datafile for. - - Returns: - directory: Map{ Name: str, Hash: str, Size: int }: - A random directory that lead to a datafile. - """ - # --- If the size of directory is small, it is leads to data file, return the data file. - if file_meta["Size"] <= self.datafile_size_bound: - return file_meta - - # --- Else, the directory leads to more directories, return a random data file within the directories. - else: - response = self.get_ipfs_directory(self.text_dir, file_meta) - # --- Return none if the request failed. - if (response == None) or (response.status_code != 200): - logger.warning( - "Failed to retrieve directory, ignoring directory:".ljust(20) - + "{}".format(file_meta) - ) - return None - - # --- Pick a random sub_directory, run recursion until we have found a data file - else: - sub_directories = response.json() - if ( - sub_directories - and "Links" in sub_directories.keys() - and len(sub_directories["Links"]) >= 1 - ): - random_sub_directory = random.choice(sub_directories["Links"]) - - # --- Fill the name of the random_sub_directory if it is empty. - if random_sub_directory["Name"] == "": - random_sub_directory["Name"] = file_meta["Name"] - random_sub_directory["Folder"] = file_meta["Folder"] - - return self.get_root_text_hash(random_sub_directory) - else: - logger.warning( - "Directory seems empty, ignoring directory:".ljust(20) - + "{}".format(file_meta) - ) - return None - - def get_text_from_local(self, min_data_len): - folders = os.listdir(os.path.expanduser(self.data_dir)) - if self.dataset_names == ["default"]: - folders_avail = folders - random.shuffle(folders_avail) - folders_avail = folders_avail[: self.max_datasets] - else: - folders_avail = [] - for dataset_name in self.dataset_names: - if dataset_name in folders: - folders_avail.append(dataset_name) - random.shuffle(folders_avail) - - files = [] - for folder in folders_avail: - file_names = os.listdir( - os.path.expanduser(os.path.join(self.data_dir, folder)) - ) - sub_files = [ - {"Name": file_name, "Folder": folder, "Hash": file_name} - for file_name in file_names - ] - files += sub_files - - random.shuffle(files) - data_corpus = [] - total_dataset_len = 0 - - for text_file in files: - # --- Get text from the datafile directory - text = self.load_hash(text_file) - - if text != None: - text_list = text.split() - data_corpus.extend(text_list) - total_dataset_len += len(text_list) - - if total_dataset_len > min_data_len: - break - - return data_corpus - - def construct_text_corpus(self, min_data_len=0): - """Main function for generating the text data. - 1. Get directories from a random dataset_hash (dataset_hash is the result from calling pin/ls). - 2. Pick a random directory and get the directory that would lead to a datafile. - 3. Get text from the directory. - 4. Repeat 2,3 until we have reached the min data length - - Returns: - text: str: - Contents of the text data. - """ - self.IPFS_fails = 0 - data_corpus = [] - try: - # --- Get directories from a random dataset_hash - directories = list(self.get_hashes_from_dataset()) - - # --- Generate a random order of the directories - random.shuffle(directories) - - # --- Pick random directories and get their text contents. - if directories: - total_dataset_size = 0 - total_dataset_len = 0 - i = 0 - - # --- Dont stop until the corpus size and the minimum data_length was reached. - n_workers = cpu_count() if self.num_workers == 0 else self.num_workers - with concurrent.futures.ThreadPoolExecutor( - max_workers=n_workers - ) as executor: - while (total_dataset_len < min_data_len) and ( - self.IPFS_fails <= self.IPFS_fails_max - ): - future_map = {} - for idx, call_arg in enumerate(directories[:n_workers]): - future = executor.submit(self.get_text, call_arg) - future_map[future] = call_arg - - for i, future in enumerate( - concurrent.futures.as_completed(future_map) - ): - text = future.result() - if text is not None: - text_list = text.split() - data_corpus.extend(text_list) - total_dataset_len += len(text_list) - - logger.success( - "Loaded from IPFS".ljust(20) - + f"{ round(total_dataset_len / min_data_len * 100) }% " - + "{}".format( - [ - file_meta["Name"] - for file_meta in directories[:n_workers] - ] - ) - ) - directories = directories[n_workers:] - - else: - logger.error( - "It appears the directory is empty... Restart your miner to try again." - ) - - except Exception as e: - logger.error( - "Ran into exception when trying to retrieve dataset from IPFS: {}".format( - e - ) - ) - - if len(data_corpus) == 0: - logger.error( - "Fail to construct any text from IPFS, getting from local instead." - ) - data_corpus = self.get_text_from_local(min_data_len) - - return data_corpus - - def reserve_multiple_data(self, epoch_length=100, multiples=2): - r"""Make sure the reserved data meet the multiple, - If not, then keep constructing text corpus. - Arg: - epoch_length (int, optional): - A dataloader for a subset of the dataset of epoch_length is returned. - - multiples (int, optional): - The number of dataloader that the data_reserved should be able to create. - - Return: - success (bool): - If we have got the data ready. - """ - logger.success(f"Reserving data with multiples: {multiples}") - data_size = epoch_length * self.batch_size * self.block_size - - while len(self.data_reserved) < data_size * multiples: - self.data_reserved += self.construct_text_corpus(min_data_len=data_size) - - logger.success(f"Dataset download completed, {multiples} copy of data reserved") - return True - - def set_data_size(self, batch_size, block_size): - r"""Update the size of data (batch_size, block_size) that we need. - - Args: - batch_size(int, required): - The batch_size of data that should be produced by dataloader. - - block_size(int, required): - The block_size of data that should be produced by dataloader. - """ - - def check_valid(size): - r"""Check if the size is a valid positive intiget, if not, return False.""" - if size <= 0 or (not isinstance(size, int)): - return False - else: - return True - - old_batch_size = self.batch_size - old_block_size = self.block_size - - if check_valid(batch_size): - self.batch_size = batch_size - - if check_valid(block_size): - self.block_size = block_size - - # empty the queue - while not self.data_queue.queue.empty(): - self.data_queue.queue.get() - - # empty the dataset_iterator with the old sizing - self.__infinite_dataset_iterator = iter([]) - - logger.success( - f"Updated data size: batch_size: {old_batch_size} --> {self.batch_size}, block_size: {old_block_size} --> {self.block_size}" - ) - - def dataloader(self, epoch_length=100): - r"""Creates a torch dataloader out of a subclass of this class. - - Args: - epoch_length (int, optional): - A dataloader for a subset of the dataset of epoch_length is returned. - - Returns: - torch.utils.data.dataloader.DataLoader: Pytorch dataloader. - """ - logger.success(f"Getting a new Dataloader") - data_size = epoch_length * self.batch_size * self.block_size - if len(self.data_reserved) < data_size: - self.reserve_multiple_data(self.num_batches, 1) - - self.data = self.data_reserved[:data_size] - - del self.data_reserved[:data_size] - - # Datalaoder calls self._getitem_ functions until the self.data uses up, and group the result by batch size - return DataLoader( - self, - shuffle=True, - batch_size=self.batch_size, - num_workers=self.num_workers, - drop_last=True, - ) - - def set_dataset_iterator(self): - r"""Get a new dataset that is ready from the queue. The result would be updated to self.__infinite_dataset_iterator__ .""" - success = False - while not success: - if not self.data_queue.queue.empty(): - ready = ( - self.data_queue.queue.get() - ) # the queue stores a bool ready signal - dataset = self.dataloader(self.num_batches) - if dataset: - self.__infinite_dataset_iterator = iter( - [input for input in dataset] - ) - success = True - else: - time.sleep(2) - - return - - def __next__(self): - """Returns the next element from the dataset.""" - if self.__infinite_dataset_iterator == None: - self.set_dataset_iterator() - - try: - return next(self.__infinite_dataset_iterator) - - except StopIteration: - self.set_dataset_iterator() - return next(self.__infinite_dataset_iterator) - - def __len__(self): - """Returns number of samples (blocks) of dataset - - Returns: - length: int - """ - if (self.data == None) or (self.block_size == None) or (self.block_size == 0): - return 0 - return round(len(self.data) / self.block_size) - - def __getitem__(self, idx: int) -> Union[str, torch.tensor]: - """Returns a block of sentences from text dataset. - - Args: - idx: index of data input - - Returns: - torch.tensor(dix) - """ - start_idx = (idx * self.block_size) % len(self.data) - end_idx = start_idx + self.block_size - text = " ".join(self.data[start_idx:end_idx]) - - if self.no_tokenizer is True: - return text - else: - tokens = self.tokenizer(text, padding=True, truncation=True)["input_ids"] - return torch.tensor(tokens, dtype=torch.long)[: self.block_size] - - def build_hash_table(self): - self.IPFS_fails = 0 - self.dataset_hashes = {} - response = None - - mountain_meta = { - "Name": "mountain", - "Folder": "meta_data", - "Hash": self.mountain_hash, - } - - while response == None: - self.IPFS_fails += 1 - response = self.get_ipfs_directory(self.text_dir, mountain_meta) - - if response: - dataset_hashes = response.json()["Links"] - if self.save_dataset: - self.save_hash(mountain_meta, json.dumps(dataset_hashes)) - - if self.IPFS_fails > self.IPFS_fails_max and response == None: - dataset_hashes = json.loads(self.load_hash(mountain_meta)) - break - - for i in dataset_hashes: - name = i["Name"][:-4] - dataset_meta = { - "Name": name, - "Hash": i["Hash"], - "Size": self.get_folder_size(name), - } - self.dataset_hashes[name] = dataset_meta diff --git a/bittensor/_dataset/dataset_mock.py b/bittensor/_dataset/dataset_mock.py deleted file mode 100644 index 3cb4a1c108..0000000000 --- a/bittensor/_dataset/dataset_mock.py +++ /dev/null @@ -1,149 +0,0 @@ -""" Implementation for the mock dataset which returns dummy tokenized text. -""" -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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. - -from torch.utils.data.dataloader import DataLoader -import torch -from loguru import logger -import bittensor -from . import dataset_impl - - -logger = logger.opt(colors=True) - - -class MockGenesisTextDataset(dataset_impl.Dataset): - def __init__( - self, - block_size, - batch_size, - num_workers, - dataset_names, - data_dir, - save_dataset, - max_datasets, - no_tokenizer, - num_batches, - ): - super().__init__() - self.block_size = block_size - self.batch_size = batch_size - self.num_workers = num_workers - self.tokenizer = bittensor.tokenizer(version=bittensor.__version__) - self.dataset_names = dataset_names - self.data_dir = data_dir - self.save_dataset = save_dataset - self.datafile_size_bound = 262158 - self.max_datasets = max_datasets - self.__infinite_dataset_iterator = None - self.no_tokenizer = no_tokenizer - - # Retrieve a random slice of the genesis dataset - self.data = [] - self.data_remained = [] - - def close(self): - pass - - def __del__(self): - self.close() - - def construct_text_corpus(self, min_data_len=0): - data_corpus = [] - total_dataset_len = 0 - i = 0 - while total_dataset_len < min_data_len: - text = "lorem ipsum data is not here this is super fake but maybe you could still learn from it?" - text_list = text.split() - data_corpus.extend(text_list) - total_dataset_len += len(text_list) - i += 1 - return data_corpus - - def _fill_data(self, epoch_length: int = 100): - data_size = epoch_length * self.batch_size * self.block_size - - # Make sure the data remained is at least as big as data_size - while len(self.data_remained) < (data_size): - self.data_remained += self.construct_text_corpus(min_data_len=data_size) - - self.data = self.data_remained[:data_size] - del self.data_remained[:data_size] - - def dataloader(self, epoch_length=100): - """Creates a torch dataloader out of a subclass of this class. - - Args: - epoch_length (int, optional): The epoch length of the miner. If this length is not set or if it is larger than the dataset, - then a dataloader for the entire dataset is returned. Otherwise, a dataloader for a subset of the dataset of epoch_length - is returned. Defaults to None. - - Returns: - torch.utils.data.dataloader.DataLoader: Pytorch dataloader. - """ - self._fill_data(epoch_length) - return DataLoader( - self, - shuffle=True, - batch_size=self.batch_size, - num_workers=self.num_workers, - drop_last=True, - ) - - def __next__(self): - """Returns the next element from the dataset.""" - if self.__infinite_dataset_iterator == None: - self.__infinite_dataset_iterator = iter(list(self.dataloader())) - try: - return next(self.__infinite_dataset_iterator) - except StopIteration: - self.__infinite_dataset_iterator = iter(list(self.dataloader())) - return next(self.__infinite_dataset_iterator) - - def __len__(self): - """Returns number of samples (blocks) of dataset - - Returns: - length: int - """ - if (self.data == None) or (self.block_size == None) or (self.block_size == 0): - return 0 - return round(len(self.data) / self.block_size) - - def __getitem__(self, idx): - """Returns a block of sentences from text dataset. - - Args: - idx: index of data input - - Returns: - torch.tensor(dix) - """ - start_idx = (idx * self.block_size) % len(self.data) - end_idx = start_idx + self.block_size - if self.no_tokenizer == False: - tokenized_text = torch.tensor( - self.tokenizer(" ".join(self.data[start_idx:end_idx]), truncation=True)[ - "input_ids" - ], - dtype=torch.long, - ) - elif self.no_tokenizer == True: - tokenized_text = " ".join(self.data[start_idx:end_idx]) - - return tokenized_text[: self.block_size] diff --git a/bittensor/_dataset/thread_queue.py b/bittensor/_dataset/thread_queue.py deleted file mode 100644 index 121debb969..0000000000 --- a/bittensor/_dataset/thread_queue.py +++ /dev/null @@ -1,95 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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 threading -import time -import queue -from loguru import logger - - -class ProducerThread(threading.Thread): - r"""This producer thread runs in backgraound to fill the queue with the result of the target function.""" - - def __init__(self, queue, target, arg, name=None): - r"""Initialization. - Args: - queue (:obj:`queue.Queue`, `required`) - The queue to be filled. - - target (:obj:`function`, `required`) - The target function to run when the queue is not full. - - arg (:type:`tuple`, `required`) - The arguments to be passed to the target function. - - name (:type:`str`, `optional`) - The name of this threading object. - """ - super(ProducerThread, self).__init__() - self.name = name - self.target = target - self.arg = arg - self.queue = queue - self._stop_event = threading.Event() - - def run(self): - r"""Work of the thread. Keep checking if the queue is full, if it is not full, run the target function to fill the queue.""" - while not self.stopped(): - if not self.queue.full(): - item = self.target(*self.arg, self.queue.qsize() + 1) - self.queue.put(item) - time.sleep(2) - return - - def stop(self): - self._stop_event.set() - - def stopped(self): - return self._stop_event.is_set() - - -class ThreadQueue: - r"""Manages the queue the producer thread that monitor and fills the queue.""" - - def __init__(self, producer_target, producer_arg, buffer_size=2): - """Setup the queue and start the producer thread. - - Args: - - producer_target (:obj:`function`, `required`) - The target function to run when the queue is not full. - - producer_arg (:type:`tuple`, `required`) - The arguments to be passed to the target function. - - buffer_size (:type:`int`, `optional`) - The size of the queue. - """ - self.buffer_size = buffer_size - self.queue = queue.Queue(buffer_size) - self.producer = ProducerThread( - name="producer", queue=self.queue, target=producer_target, arg=producer_arg - ) - self.producer.start() - - def __del__(self): - self.close() - - def close(self): - self.producer.stop() - self.producer.join() - logger.success("Dataset Thread Queue Closed") diff --git a/bittensor/_dendrite/dendrite.py b/bittensor/_dendrite/dendrite.py deleted file mode 100644 index d54c3a948c..0000000000 --- a/bittensor/_dendrite/dendrite.py +++ /dev/null @@ -1,287 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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 uuid -import grpc -import time -import torch -import asyncio -import bittensor - -from grpc import _common -from typing import Union, Optional, Callable, List, Tuple -from dataclasses import dataclass -from abc import ABC, abstractmethod - - -@dataclass -class DendriteCall(ABC): - """Base class for all dendrite calls.""" - - is_forward: bool - name: str - - def __init__( - self, dendrite: "bittensor.Dendrite", timeout: float = bittensor.__blocktime__ - ): - self.dendrite = dendrite - self.completed = False - self.timeout = timeout - self.start_time = time.time() - self.elapsed_time = 0.0 - self.src_version = bittensor.__version_as_int__ - self.dest_hotkey = self.dendrite.axon_info.hotkey - self.dest_version = self.dendrite.axon_info.version - self.return_code: bittensor.proto.ReturnCode = ( - bittensor.proto.ReturnCode.Success - ) - self.return_message: str = "Success" - - def __repr__(self) -> str: - return f"DendriteCall( {bittensor.utils.codes.code_to_string(self.return_code)}, to:{self.dest_hotkey[:4]} + ... + {self.dest_hotkey[-4:]}, msg:{self.return_message})" - - def __str__(self) -> str: - return self.__repr__() - - @abstractmethod - def get_callable(self) -> Callable: - ... - - @abstractmethod - def get_inputs_shape(self) -> torch.Size: - ... - - @abstractmethod - def get_outputs_shape(self) -> torch.Size: - ... - - @abstractmethod - def get_request_proto(self) -> object: - ... - - def _get_request_proto(self) -> object: - request_proto = self.get_request_proto() - request_proto.version = self.src_version - request_proto.timeout = self.timeout - return request_proto - - @abstractmethod - def apply_response_proto(self, response_proto: object): - ... - - def _apply_response_proto(self, response_proto: object): - self.apply_response_proto(response_proto) - try: - self.return_message = response_proto.return_message - except: - pass - try: - self.return_code = response_proto.return_code - except: - pass - - def end(self): - self.end_time = time.time() - self.elapsed = self.end_time - self.start_time - self.completed = True - - @property - def did_timeout(self) -> bool: - return self.return_code == bittensor.proto.ReturnCode.Timeout - - @property - def is_success(self) -> bool: - return self.return_code == bittensor.proto.ReturnCode.Success - - @property - def did_fail(self) -> bool: - return not self.is_success - - def log_outbound(self): - bittensor.logging.rpc_log( - axon=False, - forward=self.is_forward, - is_response=False, - code=self.return_code, - call_time=0, - pubkey=self.dest_hotkey, - uid=self.dendrite.uid, - inputs=self.get_inputs_shape(), - outputs=self.get_outputs_shape(), - message=self.return_message, - synapse=self.name, - ) - - def log_inbound(self): - bittensor.logging.rpc_log( - axon=False, - forward=self.is_forward, - is_response=True, - code=self.return_code, - call_time=self.elapsed, - pubkey=self.dest_hotkey, - uid=self.dendrite.uid, - inputs=self.get_inputs_shape(), - outputs=self.get_outputs_shape(), - message=self.return_message, - synapse=self.name, - ) - - -class Dendrite(ABC, torch.nn.Module): - def __init__( - self, - keypair: Union["bittensor.Wallet", "bittensor.Keypair"], - axon: Union["bittensor.axon_info", "bittensor.axon"], - uid: int = 0, - ip: str = None, - grpc_options: List[Tuple[str, object]] = [ - ("grpc.max_send_message_length", -1), - ("grpc.max_receive_message_length", -1), - ("grpc.keepalive_time_ms", 100000), - ], - ): - """Dendrite abstract class - Args: - keypair (:obj:`Union[ 'bittensor.Wallet', 'bittensor.Keypair']`, `required`): - bittensor keypair used for signing messages. - axon (:obj:Union[`bittensor.axon_info`, 'bittensor.axon'], `required`): - bittensor axon object or its info used to create the connection. - grpc_options (:obj:`List[Tuple[str,object]]`, `optional`): - grpc options to pass through to channel. - """ - super(Dendrite, self).__init__() - self.uuid = str(uuid.uuid1()) - self.uid = uid - self.ip = ip - self.keypair = ( - keypair.hotkey if isinstance(keypair, bittensor.Wallet) else keypair - ) - self.axon_info = axon.info() if isinstance(axon, bittensor.axon) else axon - if self.axon_info.ip == self.ip: - self.endpoint_str = "localhost:" + str(self.axon_info.port) - else: - self.endpoint_str = self.axon_info.ip + ":" + str(self.axon_info.port) - self.channel = grpc.aio.insecure_channel( - self.endpoint_str, options=grpc_options - ) - self.state_dict = _common.CYGRPC_CONNECTIVITY_STATE_TO_CHANNEL_CONNECTIVITY - self.loop = asyncio.get_event_loop() - - async def apply(self, dendrite_call: "DendriteCall") -> DendriteCall: - """Applies a dendrite call to the endpoint. - Args: - dendrite_call (:obj:`DendriteCall`, `required`): - Dendrite call to apply. - Returns: - DendriteCall: Dendrite call with response. - """ - bittensor.logging.trace("Dendrite.apply()") - try: - dendrite_call.log_outbound() - asyncio_future = dendrite_call.get_callable()( - request=dendrite_call._get_request_proto(), - timeout=dendrite_call.timeout, - metadata=( - ("rpc-auth-header", "Bittensor"), - ("bittensor-signature", self.sign()), - ("bittensor-version", str(bittensor.__version_as_int__)), - ), - ) - bittensor.logging.trace( - "Dendrite.apply() awaiting response from: {}".format( - self.axon_info.hotkey - ) - ) - response_proto = await asyncio.wait_for( - asyncio_future, timeout=dendrite_call.timeout - ) - dendrite_call._apply_response_proto(response_proto) - bittensor.logging.trace( - "Dendrite.apply() received response from: {}".format( - self.axon_info.hotkey - ) - ) - - # Request failed with GRPC code. - except grpc.RpcError as rpc_error_call: - dendrite_call.return_code = rpc_error_call.code() - dendrite_call.return_message = "GRPC error code: {}, details: {}".format( - rpc_error_call.code(), str(rpc_error_call.details()) - ) - bittensor.logging.trace( - "Dendrite.apply() rpc error: {}".format(dendrite_call.return_message) - ) - - # Catch timeout errors. - except asyncio.TimeoutError: - dendrite_call.return_code = bittensor.proto.ReturnCode.Timeout - dendrite_call.return_message = "GRPC request timeout after: {}s".format( - dendrite_call.timeout - ) - bittensor.logging.trace( - "Denrite.apply() timeout error: {}".format(dendrite_call.return_message) - ) - - except Exception as e: - # Catch unknown errors. - dendrite_call.return_code = bittensor.proto.ReturnCode.UnknownException - dendrite_call.return_message = str(e) - bittensor.logging.trace( - "Dendrite.apply() unknown error: {}".format( - dendrite_call.return_message - ) - ) - - finally: - dendrite_call.end() - dendrite_call.log_inbound() - dendrite_call.elapsed_time = time.time() - dendrite_call.start_time - return dendrite_call - - def __exit__(self): - self.__del__() - - def close(self): - self.__exit__() - - def __del__(self): - try: - result = self.channel._channel.check_connectivity_state(True) - if self.state_dict[result] != self.state_dict[result].SHUTDOWN: - self.loop.run_until_complete(self.channel.close()) - except: - pass - - def nonce(self): - return time.monotonic_ns() - - def sign(self) -> str: - """Creates a signature for the dendrite and returns it as a string.""" - nonce = f"{self.nonce()}" - sender_hotkey = self.keypair.ss58_address - receiver_hotkey = self.axon_info.hotkey - message = f"{nonce}.{sender_hotkey}.{receiver_hotkey}.{self.uuid}" - signature = f"0x{self.keypair.sign(message).hex()}" - return ".".join([nonce, sender_hotkey, signature, self.uuid]) - - def state(self): - """Returns the state of the dendrite channel.""" - try: - return self.state_dict[self.channel._channel.check_connectivity_state(True)] - except ValueError: - return "Channel closed" diff --git a/bittensor/_dendrite/text_prompting/__init__.py b/bittensor/_dendrite/text_prompting/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bittensor/_dendrite/text_prompting/dendrite.py b/bittensor/_dendrite/text_prompting/dendrite.py deleted file mode 100644 index 46275ab51d..0000000000 --- a/bittensor/_dendrite/text_prompting/dendrite.py +++ /dev/null @@ -1,221 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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 json -import torch -import bittensor -from typing import Callable, List, Union - - -class DendriteForwardCall(bittensor.DendriteCall): - name: str = "text_prompting_forward" - is_forward: bool = True - completion: str = "" # To be filled. - - def __init__( - self, - dendrite: "bittensor.TextPromptingDendrite", - messages: List[str], - roles: List[str], - timeout: float = bittensor.__blocktime__, - ): - super().__init__(dendrite=dendrite, timeout=timeout) - self.messages = messages - self.roles = roles - self.packed_messages = [ - json.dumps({"role": role, "content": message}) - for role, message in zip(self.roles, self.messages) - ] - - def __repr__(self) -> str: - return f"DendriteForwardCall( {bittensor.utils.codes.code_to_string(self.return_code)}, to: {self.dest_hotkey[:4]}...{self.dest_hotkey[-4:]}, msg: {self.return_message}, completion: {self.completion.strip()})" - - def __str__(self) -> str: - return self.__repr__() - - def get_callable(self) -> Callable: - return bittensor.grpc.TextPromptingStub(self.dendrite.channel).Forward - - def get_request_proto(self) -> bittensor.proto.ForwardTextPromptingRequest: - return bittensor.ForwardTextPromptingRequest( - timeout=self.timeout, messages=self.packed_messages - ) - - def apply_response_proto( - self, response_proto: bittensor.ForwardTextPromptingResponse - ): - self.completion = response_proto.response - - def get_inputs_shape(self) -> torch.Size: - return torch.Size([len(message) for message in self.packed_messages]) - - def get_outputs_shape(self) -> torch.Size: - return torch.Size([len(self.completion)]) - - def backward(self, reward: float, timeout: float = None) -> "DendriteBackwardCall": - return self.dendrite.backward( - roles=self.roles, - messages=self.messages, - completion=self.completion, - rewards=[reward], - timeout=self.timeout if timeout is None else bittensor.__blocktime__, - ) - - async def async_backward( - self, reward: float, timeout: float = None - ) -> "DendriteBackwardCall": - return await self.dendrite.async_backward( - roles=self.roles, - messages=self.messages, - completion=self.completion, - rewards=[reward], - timeout=self.timeout if timeout is None else bittensor.__blocktime__, - ) - - -class DendriteBackwardCall(bittensor.DendriteCall): - name: str = "text_prompting_backward" - is_forward: bool = False - - def __init__( - self, - dendrite: "bittensor.TextPromptingDendrite", - completion: str, - messages: List[str], - roles: List[str], - rewards: Union[List[float], torch.FloatTensor], - timeout: float = bittensor.__blocktime__, - ): - super().__init__(dendrite=dendrite, timeout=timeout) - self.messages = messages - self.roles = roles - self.completion = completion - self.rewards = ( - rewards if not isinstance(rewards, torch.FloatTensor) else rewards.tolist() - ) - self.packed_messages = [ - json.dumps({"role": role, "content": message}) - for role, message in zip(self.roles, self.messages) - ] - - def __repr__(self) -> str: - return f"DendriteBackwardCall( {bittensor.utils.codes.code_to_string(self.return_code)}, to: {self.dest_hotkey[:4]}...{self.dest_hotkey[-4:]}, msg: {self.return_message} )" - - def __str__(self) -> str: - return self.__repr__() - - def get_callable(self) -> Callable: - return bittensor.grpc.TextPromptingStub(self.dendrite.channel).Backward - - def get_request_proto(self) -> bittensor.proto.BackwardTextPromptingRequest: - return bittensor.BackwardTextPromptingRequest( - messages=self.packed_messages, - response=self.completion, - rewards=self.rewards, - timeout=self.timeout, - ) - - def apply_response_proto( - self, response_proto: bittensor.ForwardTextPromptingResponse - ): - pass - - def get_inputs_shape(self) -> torch.Size: - return torch.Size([len(message) for message in self.packed_messages]) - - def get_outputs_shape(self) -> torch.Size: - return torch.Size([0]) - - -class TextPromptingDendrite(bittensor.Dendrite): - def get_stub(self, channel) -> Callable: - return bittensor.grpc.TextPromptingStub(channel) - - def forward( - self, - roles: List[str], - messages: List[str], - timeout: float = bittensor.__blocktime__, - return_call: bool = True, - ) -> Union[str, DendriteForwardCall]: - forward_call = DendriteForwardCall( - dendrite=self, - messages=messages, - roles=roles, - timeout=timeout, - ) - response_call = self.loop.run_until_complete( - self.apply(dendrite_call=forward_call) - ) - if return_call: - return response_call - else: - return response_call.completion - - async def async_forward( - self, - roles: List[str], - messages: List[str], - timeout: float = bittensor.__blocktime__, - return_call: bool = True, - ) -> Union[str, DendriteForwardCall]: - forward_call = DendriteForwardCall( - dendrite=self, - messages=messages, - roles=roles, - timeout=timeout, - ) - forward_call = await self.apply(dendrite_call=forward_call) - if return_call: - return forward_call - else: - return forward_call.completion - - def backward( - self, - roles: List[str], - messages: List[str], - completion: str, - rewards: Union[List[float], torch.FloatTensor], - timeout: float = bittensor.__blocktime__, - ) -> DendriteBackwardCall: - backward_call = DendriteBackwardCall( - dendrite=self, - completion=completion, - messages=messages, - roles=roles, - rewards=rewards, - timeout=timeout, - ) - return self.loop.run_until_complete(self.apply(dendrite_call=backward_call)) - - async def async_backward( - self, - roles: List[str], - messages: List[str], - completion: str, - rewards: Union[List[float], torch.FloatTensor], - timeout: float = bittensor.__blocktime__, - ) -> DendriteBackwardCall: - backward_call = DendriteBackwardCall( - dendrite=self, - completion=completion, - messages=messages, - roles=roles, - rewards=rewards, - timeout=timeout, - ) - return await self.apply(dendrite_call=backward_call) diff --git a/bittensor/_dendrite/text_prompting/dendrite_pool.py b/bittensor/_dendrite/text_prompting/dendrite_pool.py deleted file mode 100644 index 13d714b4b1..0000000000 --- a/bittensor/_dendrite/text_prompting/dendrite_pool.py +++ /dev/null @@ -1,132 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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 grpc -import json -import torch -import asyncio -import bittensor -from typing import Callable, List, Dict, Union - - -class TextPromptingDendritePool(torch.nn.Module): - def __init__( - self, - keypair: Union["bittensor.Wallet", "bittensor.Keypair"], - metagraph: "bittensor.metagraph", - ): - super(TextPromptingDendritePool, self).__init__() - self.metagraph = metagraph - self.keypair = keypair - self.ip = bittensor.utils.networking.get_external_ip() - self.dendrites = [ - bittensor.text_prompting( - axon=axon, keypair=self.keypair, uid=uid, ip=self.ip - ) - for uid, axon in enumerate(self.metagraph.axons) - ] - self.loop = asyncio.get_event_loop() - self.priority_threadpool = bittensor.prioritythreadpool(max_workers=1) - - def backward( - self, - forward_calls: List["DendriteForwardCall"], - rewards: Union[List[float], torch.FloatTensor], - timeout: float = 12.0, - priority: int = 1, - ): - def _backward(): - self.loop.run_until_complete( - self.async_backward( - forward_calls=forward_calls, - timeout=timeout, - ) - ) - - future = self.priority_threadpool.submit(_backward, priority=priority) - return future.result() - - async def async_backward( - self, - forward_calls: List["DendriteForwardCall"], - rewards: Union[List[float], torch.FloatTensor], - timeout: float = 12.0, - ): - rewards = rewards if not isinstance(rewards, torch.Tensor) else rewards.tolist() - - async def query(): - coroutines = [ - forward_calls.async_backward(reward) - for call, reward in list(zip(forward_calls, rewards)) - ] - all_responses = await asyncio.gather(*coroutines) - return all_responses - - await query() - - def forward( - self, - roles: Union[str, List[str]], - messages: Union[str, List[str]], - uids: Union[torch.LongTensor, List[int]] = None, - return_call: bool = True, - timeout: float = 12, - priority: int = 1, - ) -> List["DendriteForwardCall"]: - def _forward(): - bittensor.logging.trace("dendrite pool: forward: _forward: start") - return self.loop.run_until_complete( - self.async_forward( - messages=messages, - roles=roles, - uids=uids, - return_call=return_call, - timeout=timeout, - ) - ) - - future = self.priority_threadpool.submit(_forward, priority=priority) - return future.result() - - async def async_forward( - self, - roles: Union[str, List[str]], - messages: Union[str, List[str]], - uids: Union[torch.LongTensor, List[int]] = None, - return_call: bool = True, - timeout: float = 12, - ) -> List["DendriteForwardCall"]: - # We optionally set the uids to all if uids is None. - if uids is None: - uids = range(self.metagraph.n.item()) - if isinstance(uids, torch.Tensor): - uids = uids.tolist() - - # The following asyncio defintion queries a single endpoint with the message - # prompt and returns the response. - async def call_single_uid(uid: int) -> str: - return await self.dendrites[uid].async_forward( - roles=roles, messages=messages, return_call=return_call, timeout=timeout - ) - - # The following asyncio definition gathers the responses - # from multiple coroutines for each uid. - async def query(): - coroutines = [call_single_uid(uid) for uid in uids] - all_responses = await asyncio.gather(*coroutines) - return all_responses - - return await query() diff --git a/bittensor/_ipfs/__init__.py b/bittensor/_ipfs/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bittensor/_ipfs/ipfs_impl.py b/bittensor/_ipfs/ipfs_impl.py deleted file mode 100644 index aa08840785..0000000000 --- a/bittensor/_ipfs/ipfs_impl.py +++ /dev/null @@ -1,77 +0,0 @@ -from socket import timeout -from requests.adapters import HTTPAdapter -from requests.packages.urllib3.util.retry import Retry -import requests - - -class Ipfs: - """Implementation for the dataset class, which handles dataloading from ipfs""" - - def __init__(self): - # Used to retrieve directory contentx - self.cat = "http://global.ipfs.opentensor.ai/api/v0/cat" - self.node_get = "http://global.ipfs.opentensor.ai/api/v0/object/get" - self.ipns_resolve = "http://global.ipfs.opentensor.ai/api/v0/name/resolve" - - self.mountain_hash = "QmSdDg6V9dgpdAFtActs75Qfc36qJtm9y8a7yrQ1rHm7ZX" - self.latest_neurons_ipns = ( - "k51qzi5uqu5di1eoe0o91g32tbfsgikva6mvz0jw0414zhxzhiakana67shoh7" - ) - self.historical_neurons_ipns = ( - "k51qzi5uqu5dhf5yxm3kqw9hyrv28q492p3t32s23059z911a23l30ai6ziceh" - ) - # Used when current corpus has been exhausted - self.refresh_corpus = False - - @staticmethod - def requests_retry_session( - retries=1, - backoff_factor=0.5, - status_forcelist=(104, 500, 502, 504), - session=None, - ): - """Creates a retriable session for request calls. This enables - automatic retries and back-off retries should any request calls fail. - - Args: - retries (int, optional): Maximum number of retries. Defaults to 3. - backoff_factor (float, optional): Factor by which to back off if a retry fails. Defaults to 0.3. - status_forcelist (tuple, optional): A set of integer HTTP status codes that we should force a retry on. Defaults to (500, 502, 504). - session ([type], optional): Session for which to set up the retries. Defaults to None. - - Returns: - requests.Session(): A Requests Session object set up for retries and backoff. - """ - - session = session or requests.Session() - retry = Retry( - total=retries, - read=retries, - connect=retries, - backoff_factor=backoff_factor, - status_forcelist=status_forcelist, - ) - adapter = HTTPAdapter(max_retries=retry) - session.mount("http://", adapter) - session.mount("https://", adapter) - return session - - def retrieve_directory( - self, address: str, params=None, action: str = "post", timeout: int = 180 - ): - r"""Connects to Pinata IPFS gateway and retrieves directory. - - Returns: - dict: A dictionary of the files inside of the genesis_datasets and their hashes. - """ - session = requests.Session() - session.params.update(params) - if action == "get": - response = Ipfs.requests_retry_session(session=session).get( - address, timeout=timeout - ) - elif action == "post": - response = Ipfs.requests_retry_session(session=session).post( - address, timeout=timeout - ) - return response diff --git a/bittensor/_metagraph/__init__.py b/bittensor/_metagraph/__init__.py deleted file mode 100644 index 7e94713243..0000000000 --- a/bittensor/_metagraph/__init__.py +++ /dev/null @@ -1,386 +0,0 @@ -""" Maintains chain state as a torch.nn.Module. -""" -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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 os -import torch -import bittensor - -from os import listdir -from os.path import join -from typing import List, Optional - - -# Return directory path from network and netuid -def get_save_dir(network: str, netuid: int) -> str: - return os.path.expanduser( - f"~/.bittensor/metagraphs/network-{str(network)}/netuid-{str(netuid)}/" - ) - - -def latest_block_path(dir_path: str) -> int: - latest_block = -1 - latest_file_full_path = None - for filename in listdir(dir_path): - full_path_filename = os.path.expanduser(join(dir_path, filename)) - try: - block_number = int(filename.split("-")[1].split(".")[0]) - if block_number > latest_block: - latest_block = block_number - latest_file_full_path = full_path_filename - except Exception as e: - pass - if not latest_file_full_path: - raise ValueError(f"Metagraph not found at: {dir_path}") - else: - return latest_file_full_path - - -class metagraph(torch.nn.Module): - @property - def S(self) -> torch.FloatTensor: - return self.total_stake - - @property - def R(self) -> torch.FloatTensor: - return self.ranks - - @property - def I(self) -> torch.FloatTensor: - return self.incentive - - @property - def E(self) -> torch.FloatTensor: - return self.emission - - @property - def C(self) -> torch.FloatTensor: - return self.consensus - - @property - def T(self) -> torch.FloatTensor: - return self.trust - - @property - def Tv(self) -> torch.FloatTensor: - return self.validator_trust - - @property - def D(self) -> torch.FloatTensor: - return self.dividends - - @property - def B(self) -> torch.FloatTensor: - return self.bonds - - @property - def W(self) -> torch.FloatTensor: - return self.weights - - @property - def hotkeys(self) -> List[str]: - return [axon.hotkey for axon in self.axons] - - @property - def coldkeys(self) -> List[str]: - return [axon.coldkey for axon in self.axons] - - @property - def addresses(self) -> List[str]: - return [axon.ip_str() for axon in self.axons] - - def __str__(self): - return "Metagraph(netuid:{}, n:{}, block:{}, network:{})".format( - self.netuid, self.n.item(), self.block.item(), self.network - ) - - def __repr__(self): - return self.__str__() - - def metadata(self) -> dict: - return { - "netuid": self.netuid, - "n": self.n.item(), - "block": self.block.item(), - "network": self.network, - "version": bittensor.__version__, - } - - def __init__( - self, netuid: int, network: str = "finney", lite: bool = True, sync: bool = True - ) -> "metagraph": - 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.tensor([0], dtype=torch.int64), requires_grad=False - ) - self.block = 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.tensor([], dtype=torch.float32), requires_grad=False - ) - self.ranks = torch.nn.Parameter( - torch.tensor([], dtype=torch.float32), requires_grad=False - ) - self.trust = torch.nn.Parameter( - torch.tensor([], dtype=torch.float32), requires_grad=False - ) - self.consensus = torch.nn.Parameter( - torch.tensor([], dtype=torch.float32), requires_grad=False - ) - self.validator_trust = torch.nn.Parameter( - torch.tensor([], dtype=torch.float32), requires_grad=False - ) - self.incentive = torch.nn.Parameter( - torch.tensor([], dtype=torch.float32), requires_grad=False - ) - self.emission = torch.nn.Parameter( - torch.tensor([], dtype=torch.float32), requires_grad=False - ) - self.dividends = 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.tensor([], dtype=torch.float32), requires_grad=False - ) - self.bonds = 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.axons = [] - if sync: - self.sync(block=None, lite=lite) - - def sync( - self, - block: Optional[int] = None, - lite: bool = True, - subtensor: Optional["bittensor.Subtensor"] = None, - ) -> "metagraph": - if not subtensor: - subtensor = bittensor.subtensor(network=self.network) - if lite: - self.neurons = subtensor.neurons_lite(block=block, netuid=self.netuid) - else: - self.neurons = subtensor.neurons(block=block, netuid=self.netuid) - - self.lite = lite - self.n = torch.nn.Parameter( - torch.tensor(len(self.neurons), dtype=torch.int64), requires_grad=False - ) - self.version = torch.nn.Parameter( - torch.tensor([bittensor.__version_as_int__], dtype=torch.int64), - requires_grad=False, - ) - self.block = torch.nn.Parameter( - torch.tensor(block if block else subtensor.block, dtype=torch.int64), - requires_grad=False, - ) - self.uids = torch.nn.Parameter( - torch.tensor([neuron.uid for neuron in self.neurons], dtype=torch.int64), - requires_grad=False, - ) - self.trust = torch.nn.Parameter( - torch.tensor( - [neuron.trust for neuron in self.neurons], dtype=torch.float32 - ), - requires_grad=False, - ) - self.consensus = torch.nn.Parameter( - torch.tensor( - [neuron.consensus for neuron in self.neurons], dtype=torch.float32 - ), - requires_grad=False, - ) - self.incentive = torch.nn.Parameter( - torch.tensor( - [neuron.incentive for neuron in self.neurons], dtype=torch.float32 - ), - requires_grad=False, - ) - self.dividends = torch.nn.Parameter( - torch.tensor( - [neuron.dividends for neuron in self.neurons], dtype=torch.float32 - ), - requires_grad=False, - ) - self.ranks = torch.nn.Parameter( - torch.tensor([neuron.rank for neuron in self.neurons], dtype=torch.float32), - requires_grad=False, - ) - self.emission = torch.nn.Parameter( - torch.tensor( - [neuron.emission for neuron in self.neurons], dtype=torch.float32 - ), - requires_grad=False, - ) - self.active = torch.nn.Parameter( - torch.tensor([neuron.active for neuron in self.neurons], dtype=torch.int64), - requires_grad=False, - ) - self.last_update = torch.nn.Parameter( - torch.tensor( - [neuron.last_update for neuron in self.neurons], dtype=torch.int64 - ), - requires_grad=False, - ) - self.validator_permit = torch.nn.Parameter( - torch.tensor( - [neuron.validator_permit for neuron in self.neurons], dtype=torch.bool - ), - requires_grad=False, - ) - self.validator_trust = torch.nn.Parameter( - torch.tensor( - [neuron.validator_trust for neuron in self.neurons], dtype=torch.float32 - ), - requires_grad=False, - ) - self.total_stake = torch.nn.Parameter( - torch.tensor( - [neuron.total_stake.tao for neuron in self.neurons], dtype=torch.float32 - ), - requires_grad=False, - ) - self.stake = torch.nn.Parameter( - torch.tensor( - [neuron.stake for neuron in self.neurons], dtype=torch.float32 - ), - requires_grad=False, - ) - self.axons = [n.axon_info for n in self.neurons] - if not lite: - weights_array = [] - for n in self.neurons: - if len(n.weights) == 0: - weights_array.append(torch.zeros(len(self.neurons))) - else: - w_uids, w_weights = zip(*n.weights) - weights_array.append( - bittensor.utils.weight_utils.convert_weight_uids_and_vals_to_tensor( - len(self.neurons), w_uids, w_weights - ) - ) - self.weights = ( - torch.nn.Parameter(torch.stack(weights_array), requires_grad=False) - if len(weights_array) - else torch.nn.Parameter() - ) - if len(weights_array) == 0: - bittensor.logging.warning( - "Empty weights_array on metagraph.sync(). The 'weights' tensor is empty." - ) - if not lite: - bonds_array = [] - for n in self.neurons: - if len(n.bonds) == 0: - bonds_array.append(torch.zeros(len(self.neurons))) - else: - b_uids, b_bonds = zip(*n.bonds) - bonds_array.append( - bittensor.utils.weight_utils.convert_bond_uids_and_vals_to_tensor( - len(self.neurons), b_uids, b_bonds - ) - ) - self.bonds = ( - torch.nn.Parameter(torch.stack(bonds_array), requires_grad=False) - if len(bonds_array) - else torch.nn.Parameter() - ) - if len(bonds_array) == 0: - bittensor.logging.warning( - "Empty bonds_array on metagraph.sync(). The 'bonds' tensor is empty." - ) - - def save(self) -> "metagraph": - r"""Saves this metagraph object's state_dict under bittensor root dir.""" - 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" - state_dict = self.state_dict() - state_dict["axons"] = self.axons - torch.save(state_dict, graph_file) - state_dict = torch.load(graph_file) - return self - - def load(self) -> "metagraph": - r"""Loads this metagraph object's state_dict from bittensor root dir.""" - self.load_from_path(get_save_dir(self.network, self.netuid)) - - def load_from_path(self, dir_path: str) -> "metagraph": - r"""Loads this metagraph object with state_dict under the specified path.""" - 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) - self.axons = state_dict["axons"] - if "weights" in state_dict: - self.weights = torch.nn.Parameter( - state_dict["weights"], requires_grad=False - ) - if "bonds" in state_dict: - self.bonds = torch.nn.Parameter(state_dict["bonds"], requires_grad=False) - return self diff --git a/bittensor/_neuron/__init__.py b/bittensor/_neuron/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bittensor/_neuron/base_huggingface_miner.py b/bittensor/_neuron/base_huggingface_miner.py deleted file mode 100644 index 4a6c67a6f2..0000000000 --- a/bittensor/_neuron/base_huggingface_miner.py +++ /dev/null @@ -1,183 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2023 Yuma Rao - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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 bittensor -import argparse -from typing import List, Dict -from abc import ABC, abstractmethod - - -class HuggingFaceMiner(bittensor.BasePromptingMiner, ABC): - arg_prefix: str - assistant_label: str - user_label: str - system_label: str - - @classmethod - def check_config(cls, config: "bittensor.Config"): - pass - - @classmethod - def add_args(cls, parser: argparse.ArgumentParser): - parser.add_argument( - f"--{cls.arg_prefix}.model_name", - type=str, - default=None, - help="Name or path of model to load", - ) - parser.add_argument( - f"--{cls.arg_prefix}.api_key", - type=str, - help="huggingface api key", - default=None, - ) - parser.add_argument( - f"--{cls.arg_prefix}.device", - type=str, - help="Device to load model", - default="cuda", - ) - parser.add_argument( - f"--{cls.arg_prefix}.max_new_tokens", - type=int, - help="Max tokens for model output.", - default=256, - ) - parser.add_argument( - f"--{cls.arg_prefix}.temperature", - type=float, - help="Sampling temperature of model", - default=0.5, - ) - parser.add_argument( - f"--{cls.arg_prefix}.do_sample", - action="store_true", - default=False, - help="Whether to use multinomial sampling.", - ) - parser.add_argument( - f"--{cls.arg_prefix}.repetition_penalty", - type=float, - help="Repetition penalty for model", - default=1.3, - ) - parser.add_argument( - f"--{cls.arg_prefix}.do_prompt_injection", - action="store_true", - default=False, - help='Whether to use a custom "system" prompt instead of the one sent by bittensor.', - ) - parser.add_argument( - f"--{cls.arg_prefix}.system_prompt", - type=str, - help="What prompt to replace the system prompt with", - default="BEGINNING OF CONVERSATION: ", - ) - parser.add_argument( - f"--{cls.arg_prefix}.repetition-penalty", - type=float, - default=1.1, - help="Repetition penalty for greedy decoding. Between 1.0 and infinity. 1.0 means no penalty. Default: 1.0", - ) - parser.add_argument( - f"--{cls.arg_prefix}.top_p", - type=float, - default=0.9, - help="Top-p (nucleus) sampling. Defaults to 1.0 (top-k sampling). Must be between 0.0 and 1.0.", - ) - parser.add_argument( - f"--{cls.arg_prefix}.top_k", - type=int, - default=0, - help="Top-k sampling. Defaults to 0 (no top-k sampling). Must be between 0 and 1000.", - ) - parser.add_argument( - f"--{cls.arg_prefix}.load_in_8bit", - type=bool, - default=False, - help="Load model in 8 bit precision", - ) - parser.add_argument( - f"--{cls.arg_prefix}.device_map", - type=str, - default=None, - help="Device map for model parallelism.", - ) - parser.add_argument( - f"--{cls.arg_prefix}.pad_tokens", - type=int, - default=[], - nargs="+", - help="A list of integers separated by spaces for the pad_tokens.", - ) - - def __init__(self): - super(HuggingFaceMiner, self).__init__() - - # Set model name if unset. - if getattr(self.config, self.arg_prefix).model_name == None: - getattr(self.config, self.arg_prefix).model_name = self.arg_prefix - - bittensor.logging.info( - "Loading " + str(getattr(self.config, self.arg_prefix).model_name) - ) - self.tokenizer = self.load_tokenizer() - self.model = self.load_model() - bittensor.logging.info("Model loaded!") - - # Device already configured if using pipieline or device_map is set. (i.e. Pipelines have no `.to()` method) - if ( - getattr(self.config, self.arg_prefix).device != "cpu" - and "pipeline" not in self.model.__class__.__name__.lower() - and getattr(self.config, self.arg_prefix).device_map == None - ): - self.model = self.model.to(getattr(self.config, self.arg_prefix).device) - - @abstractmethod - def load_model(self): - ... - - @abstractmethod - def load_tokenizer(self): - ... - - @abstractmethod - def forward(self, messages: List[Dict[str, str]], **kwargs) -> str: - ... - - def process_history(self, history: List[Dict[str, str]]) -> str: - processed_history = "" - - if getattr(self.config, self.arg_prefix).do_prompt_injection: - processed_history += getattr(self.config, self.arg_prefix).system_prompt - - for message in history: - if message["role"] == "system": - if ( - not getattr(self.config, self.arg_prefix).do_prompt_injection - or message != history[0] - ): - processed_history += ( - self.system_label + message["content"].strip() + " " - ) - if message["role"] == "assistant": - processed_history += ( - self.assistant_label + message["content"].strip() + "" - ) - if message["role"] == "user": - processed_history += self.user_label + message["content"].strip() + " " - return processed_history diff --git a/bittensor/_neuron/base_miner_neuron.py b/bittensor/_neuron/base_miner_neuron.py deleted file mode 100644 index 474fb7bfba..0000000000 --- a/bittensor/_neuron/base_miner_neuron.py +++ /dev/null @@ -1,245 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2023 Yuma Rao - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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 os -import time -import torch -import threading -import argparse -import bittensor - -from rich import print -from typing import Union, Tuple -from datetime import datetime - - -class BaseMinerNeuron: - def priority(self, forward_call: "bittensor.SynapseCall") -> float: - return self.prioritizer.priority(forward_call, metagraph=self.metagraph) - - def blacklist( - self, forward_call: "bittensor.SynapseCall" - ) -> Union[Tuple[bool, str], bool]: - return self.blacklister.blacklist(forward_call, metagraph=self.metagraph) - - @classmethod - def config(cls) -> "bittensor.Config": - parser = argparse.ArgumentParser() - cls.add_args(parser) - return bittensor.config(parser) - - @classmethod - def help(cls): - parser = argparse.ArgumentParser() - cls.add_args(parser) - print(cls.__new__.__doc__) - parser.print_help() - - @classmethod - def check_config(cls, config: "bittensor.Config"): - bittensor.axon.check_config(config) - bittensor.wallet.check_config(config) - bittensor.logging.check_config(config) - bittensor.subtensor.check_config(config) - full_path = os.path.expanduser( - "{}/{}/{}/{}".format( - config.logging.logging_dir, - config.wallet.get("name", bittensor.defaults.wallet.name), - config.wallet.get("hotkey", bittensor.defaults.wallet.hotkey), - config.neuron.name, - ) - ) - config.neuron.full_path = os.path.expanduser(full_path) - if not os.path.exists(config.neuron.full_path): - os.makedirs(config.neuron.full_path) - - @classmethod - def add_args(cls, parser: argparse.ArgumentParser, prefix: str = None): - prefix_str = "" if prefix is None else prefix + "." - parser.add_argument( - "--" + prefix_str + "netuid", type=int, help="Subnet netuid", default=1 - ) - parser.add_argument( - "--" + prefix_str + "neuron.name", - type=str, - help="Trials for this miner go in miner.root / (wallet_cold - wallet_hot) / miner.name ", - default="openai_prompting_miner", - ) - parser.add_argument( - "--" + prefix_str + "neuron.blocks_per_epoch", - type=str, - help="Blocks until the miner sets weights on chain", - default=100, - ) - parser.add_argument( - "--" + prefix_str + "neuron.no_set_weights", - action="store_true", - help="If True, the model does not set weights.", - default=False, - ) - parser.add_argument( - "--" + prefix_str + "neuron.reregister", - action="store_true", - help="If True, the miner will reregister on chain.", - default=False, - ) - bittensor.wallet.add_args(parser, prefix=prefix) - bittensor.axon.add_args(parser, prefix=prefix) - bittensor.subtensor.add_args(parser, prefix=prefix) - bittensor.logging.add_args(parser, prefix=prefix) - bittensor.blacklist.add_args(parser, prefix=prefix_str + "neuron") - bittensor.priority.add_args(parser, prefix=prefix_str + "neuron") - - def __init__(self, netuid: int = None, config: "bittensor.Config" = None): - super_config = ( - config if config != None else BaseMinerNeuron.config() - ) # Grab super (BaseMinerNeuron) config - child_config = self.config() # grab child (Miner) class configs. - self.config = child_config - self.config.merge( - super_config - ) # Merge the two configs. Child configs override super configs. - self.config.netuid = netuid or self.config.netuid - BaseMinerNeuron.check_config(self.config) - - # Build objects. - bittensor.logging(config=self.config, logging_dir=self.config.neuron.full_path) - self.subtensor = bittensor.subtensor(self.config) - self.wallet = bittensor.wallet(self.config) - self.metagraph = self.subtensor.metagraph(netuid=self.config.netuid) - self.metagraph.sync(lite=True, subtensor=self.subtensor) - - self.axon = bittensor.axon(wallet=self.wallet, config=self.config) - self.blacklister = bittensor.blacklist(config=self.config.neuron) - self.prioritizer = bittensor.priority(config=self.config.neuron) - - # Used for backgounr process. - self.is_running = False - self.should_exit = False - self.background_thread = None - - def attach(self, synapse: "bittensor.Synapse"): - # pass through attach function. - self.axon.attach(synapse) - - def __enter__(self): - bittensor.logging.trace("BaseMinerNeuron.__enter__()") - self.start_in_background() - return self - - def __exit__(self, exc_type, exc_value, traceback): - bittensor.logging.trace("BaseMinerNeuron.__exit__()") - self.stop() - - def start_in_background(self): - if self.is_running: - bittensor.logging.warning("The base miner neuron is already running.") - else: - self.should_exit = False - self.background_thread = threading.Thread(target=self.run, daemon=True) - self.background_thread.start() - self.is_running = True - bittensor.logging.trace("Starting the base miner neuron in the background.") - - def stop(self): - if self.is_running: - self.should_exit = True - else: - bittensor.logging.warning("The base miner neuron is not running.") - - def run(self): - bittensor.logging.debug("BaseMinerNeuron.run()") - - # --- Start the miner. - self.is_running = True - bittensor.utils.reregister( - wallet=self.wallet, - subtensor=self.subtensor, - netuid=self.config.netuid, - reregister=self.config.neuron.reregister, - ) - self.axon.start() - self.subtensor.serve_axon( - netuid=self.config.netuid, - axon=self.axon, - wait_for_finalization=False, - wait_for_inclusion=False, - ) # TODO: fix finalization & inclusion - - # --- Run Forever. - last_update = self.subtensor.get_current_block() - retries = 0 - while not self.should_exit: - # --- Wait until next epoch. - current_block = self.subtensor.get_current_block() - while (current_block - last_update) < self.config.neuron.blocks_per_epoch: - if self.should_exit: - continue - time.sleep(0.1) # bittensor.__blocktime__ - current_block = self.subtensor.get_current_block() - last_update = self.subtensor.get_current_block() - - # --- Update the metagraph with the latest network state. - try: - self.metagraph.sync(lite=True, subtensor=self.subtensor) - uid = self.metagraph.hotkeys.index(self.wallet.hotkey.ss58_address) - except: - # --- If we fail to sync the metagraph, wait and try again. - if retries > 8: - bittensor.logging.error(f"Failed to sync metagraph, exiting.") - self.stop() - break - seconds_to_sleep = 5 * 1.5 ** (retries) - bittensor.logging.error( - f"Failed to sync metagraph, retrying in {seconds_to_sleep} seconds." - ) - time.sleep(seconds_to_sleep) - retries += 1 - continue - - if retries > 0: - retries = 0 - - # --- Log performance. - print( - f"[white not bold]{datetime.now():%Y-%m-%d %H:%M:%S}[/white not bold]{' ' * 4} | " - f"{f'UID [bright_cyan]{uid}[/bright_cyan]'.center(16 + len('[bright_cyan][/bright_cyan]'))} | " - f"[dim white not bold] [green]{str(self.metagraph.S[uid].item()):.4}[/green] Stake [/dim white not bold]" - f"[dim white not bold]| [yellow]{str(self.metagraph.trust[uid].item()) :.3}[/yellow] Trust [/dim white not bold]" - f"[dim white not bold]| [green]{str(self.metagraph.incentive[uid].item()):.3}[/green] Incentive [/dim white not bold]" - ) - - # --- Set weights. - if not self.config.neuron.no_set_weights: - try: - # --- query the chain for the most current number of peers on the network - chain_weights = torch.zeros( - self.subtensor.subnetwork_n(netuid=self.config.netuid) - ) - chain_weights[uid] = 1 - did_set = self.subtensor.set_weights( - uids=torch.arange(0, len(chain_weights)), - netuid=self.config.netuid, - weights=chain_weights, - wait_for_inclusion=False, - walle=self.wallet, - version_key=1, - ) - except: - pass - - self.axon.stop() diff --git a/bittensor/_neuron/base_prompting_miner.py b/bittensor/_neuron/base_prompting_miner.py deleted file mode 100644 index 5edcd87bb4..0000000000 --- a/bittensor/_neuron/base_prompting_miner.py +++ /dev/null @@ -1,90 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2023 Yuma Rao - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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 argparse -import bittensor - -from rich import print -from typing import List, Dict, Union, Tuple -from abc import ABC, abstractmethod - - -class BasePromptingMiner(bittensor.BaseMinerNeuron, ABC): - @classmethod - @abstractmethod - def add_args(cls, parser: argparse.ArgumentParser): - ... - - @abstractmethod - def forward(self, messages: List[Dict[str, str]]) -> str: - ... - - @classmethod - @abstractmethod - def check_config(cls, config: "bittensor.Config"): - ... - - @classmethod - def config(cls) -> "bittensor.Config": - parser = argparse.ArgumentParser() - cls.add_super_args(parser) - return bittensor.config(parser) - - @classmethod - def add_super_args(cls, parser: argparse.ArgumentParser): - """Add arguments specific to BasePromptingMiner to parser.""" - cls.add_args(parser) - parser.add_argument( - "--neuron.max_batch_size", - type=int, - help="The maximum batch size for forward requests.", - default=-1, - ) - parser.add_argument( - "--neuron.max_sequence_len", - type=int, - help="The maximum sequence length for forward requests.", - default=-1, - ) - - def __init__(self, config: "bittensor.Config" = None): - super(BasePromptingMiner, self).__init__() - - class Synapse(bittensor.TextPromptingSynapse): - def priority( - _, forward_call: "bittensor.TextPromptingForwardCall" - ) -> float: - return self.priority(forward_call) - - def blacklist( - _, forward_call: "bittensor.TextPromptingForwardCall" - ) -> Union[Tuple[bool, str], bool]: - return self.blacklist(forward_call) - - def backward( - self, - messages: List[Dict[str, str]], - response: str, - rewards: torch.FloatTensor, - ) -> str: - pass - - def forward(_, messages: List[Dict[str, str]]) -> str: - return self.forward(messages) - - self.synapse = Synapse(axon=self.axon) diff --git a/bittensor/_priority/__init__.py b/bittensor/_priority/__init__.py deleted file mode 100644 index 60d5332fc0..0000000000 --- a/bittensor/_priority/__init__.py +++ /dev/null @@ -1,88 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao -# Copyright © 2022 Opentensor Foundation - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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 math -import argparse -import bittensor - - -class priority: - def __init__(self, config: "bittensor.Config" = None): - self.config = config or priority.config() - - @classmethod - def config(cls) -> "bittensor.Config": - parser = argparse.ArgumentParser() - priority.add_args(parser) - return bittensor.config(parser) - - @classmethod - def help(cls): - parser = argparse.ArgumentParser() - cls.add_args(parser) - print(cls.__new__.__doc__) - parser.print_help() - - @classmethod - def add_args(cls, parser: argparse.ArgumentParser, prefix: str = None): - prefix_str = "" if prefix is None else prefix + "." - parser.add_argument( - "--" + prefix_str + "priority.default_priority", - type=float, - help="Default call priority in queue.", - default=0.0, - ) - parser.add_argument( - "--" + prefix_str + "priority.blacklisted_keys", - type=str, - required=False, - nargs="*", - action="store", - help="List of ss58 addresses which are always given -math.inf priority", - default=[], - ) - parser.add_argument( - "--" + prefix_str + "priority.whitelisted_keys", - type=str, - required=False, - nargs="*", - action="store", - help="List of ss58 addresses which are always given math.inf priority", - default=[], - ) - - def priority( - self, - forward_call: "bittensor.SynapseCall", - metagraph: "bittensor.Metagraph" = None, - ) -> float: - # Check for blacklisted keys which take priority over all other checks. - src_hotkey = forward_call.src_hotkey - if src_hotkey in self.config.priority.blacklisted_keys: - return -math.inf - - # Check for whitelisted keys which take priority over all remaining checks. - if src_hotkey in self.config.priority.whitelisted_keys: - return math.inf - - # Otherwise priority of requests is based on stake. - if metagraph is not None: - uid = metagraph.hotkeys.index(forward_call.src_hotkey) - return metagraph.S[uid].item() - - # Default priority. - return self.config.priority.default_priority diff --git a/bittensor/_prometheus/__init__.py b/bittensor/_prometheus/__init__.py deleted file mode 100644 index efaf15a9b4..0000000000 --- a/bittensor/_prometheus/__init__.py +++ /dev/null @@ -1,219 +0,0 @@ -""" -Create and init the config class, which manages the config of different bittensor modules. -""" -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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 os -import argparse -import bittensor -from typing import List, Callable, Union -from prometheus_client import start_http_server -from enum import Enum - -from loguru import logger - -logger = logger.opt(colors=True) - - -class prometheus: - """Namespace for prometheus tooling.""" - - # Prometheus global logging levels. - class level(Enum): - OFF = "OFF" - INFO = "INFO" - DEBUG = "DEBUG" - - def __str__(self): - return self.value - - # Prometheus Global state. - port: int = None - started: bool = False - - def __new__( - cls, - wallet: "bittensor.wallet", - netuid: int, - config: "bittensor.config" = None, - port: int = None, - level: Union[str, "prometheus.level"] = None, - network: str = None, - chain_endpoint: str = None, - subtensor: "bittensor.subtensor" = None, - ): - """Instantiates a global prometheus DB which can be accessed by other processes. - Each prometheus DB is designated by a port. - Args: - wallet (:obj: `bittensor.wallet`, `required`): - bittensor wallet object. - netuid (:obj: `int`, `required`): - network uid to serve on. - config (:obj:`bittensor.Config`, `optional`, defaults to bittensor.prometheus.config()): - A config namespace object created by calling bittensor.prometheus.config() - port (:obj:`int`, `optional`, defaults to bittensor.defaults.prometheus.port ): - The port to run the prometheus DB on, this uniquely identifies the prometheus DB. - level (:obj:`prometheus.level`, `optional`, defaults to bittensor.defaults.prometheus.level ): - Prometheus logging level. If OFF, the prometheus DB is not initialized. - subtensor (:obj:`bittensor.Subtensor`, `optional`): - Chain connection through which to serve. - network (default='local', type=str) - If subtensor is not set, uses this network flag to create the subtensor connection. - chain_endpoint (default=None, type=str) - Overrides the network argument if not set. - """ - if config == None: - config = prometheus.config() - - if isinstance(level, prometheus.level): - level = level.name # Convert ENUM to str. - - if subtensor == None: - subtensor = bittensor.subtensor( - network=network, chain_endpoint=chain_endpoint - ) - - config.prometheus.port = port if port != None else config.prometheus.port - config.prometheus.level = level if level != None else config.prometheus.level - - if isinstance(config.prometheus.level, str): - config.prometheus.level = ( - config.prometheus.level.upper() - ) # Convert str to upper case. - - cls.check_config(config) - - return cls.serve( - cls, - wallet=wallet, - netuid=netuid, - subtensor=subtensor, - port=config.prometheus.port, - level=config.prometheus.level, - ) - - def serve(cls, wallet, subtensor, netuid, port, level) -> bool: - if level == prometheus.level.OFF.name: # If prometheus is off, return true. - logger.success("Prometheus:".ljust(20) + "OFF") - return True - else: - # Serve prometheus. Not OFF - serve_success = subtensor.serve_prometheus( - wallet=wallet, - port=port, - netuid=netuid, - ) - if serve_success: - try: - start_http_server(port) - except OSError: - # The singleton process is likely already running. - logger.error( - "Prometheus:".ljust(20) - + "{} already in use ".format(port) - ) - prometheus.started = True - prometheus.port = port - logger.success( - "Prometheus:".ljust(20) - + "ON".ljust(20) - + "using: [::]:{}".format(port) - ) - return True - else: - logger.error("Prometheus:".ljust(20) + "OFF") - raise RuntimeError("Failed to serve neuron.") - - @classmethod - def config(cls) -> "bittensor.Config": - """Get config from the argument parser - Return: bittensor.config object - """ - parser = argparse.ArgumentParser() - cls.add_args(parser=parser) - return bittensor.config(parser) - - @classmethod - def help(cls): - """Print help to stdout""" - parser = argparse.ArgumentParser() - cls.add_args(parser) - print(cls.__new__.__doc__) - parser.print_help() - - @classmethod - def add_args(cls, parser: argparse.ArgumentParser, prefix: str = None): - """Accept specific arguments from parser""" - prefix_str = "" if prefix == None else prefix + "." - if prefix is not None: - if bittensor.defaults.get(prefix, d=None) == None: - setattr(bittensor.defaults, prefix, bittensor.Config()) - getattr( - bittensor.defaults, prefix - ).prometheus = bittensor.defaults.prometheus - try: - parser.add_argument( - "--" + prefix_str + "prometheus.port", - type=int, - required=False, - default=bittensor.defaults.prometheus.port, - help="""Prometheus serving port.""", - ) - parser.add_argument( - "--" + prefix_str + "prometheus.level", - required=False, - type=str, - choices=[l.name for l in list(prometheus.level)], - default=bittensor.defaults.prometheus.level, - help="""Prometheus logging level. """, - ) - except argparse.ArgumentError as e: - pass - - @classmethod - def add_defaults(cls, defaults): - """Adds parser defaults to object from enviroment variables.""" - defaults.prometheus = bittensor.Config() - # Default the prometheus port to axon.port - 1000 - defaults.prometheus.port = ( - os.getenv("BT_PROMETHEUS_PORT") - if os.getenv("BT_PROMETHEUS_PORT") != None - else 7091 - ) - defaults.prometheus.level = ( - os.getenv("BT_PROMETHEUS_LEVEL") - if os.getenv("BT_PROMETHEUS_LEVEL") != None - else bittensor.prometheus.level.INFO.value - ) - - @classmethod - def check_config(cls, config: "bittensor.Config"): - """Check config for wallet name/hotkey/path/hotkeys/sort_by""" - assert "prometheus" in config - assert config.prometheus.level in [ - l.name for l in list(prometheus.level) - ], "config.prometheus.level must be in: {}".format( - [l.name for l in list(prometheus.level)] - ) - assert ( - config.prometheus.port > 1024 and config.prometheus.port < 65535 - ), "config.prometheus.port must be in range [1024, 65535]" - if "axon" in config and "port" in config.axon: - assert ( - config.prometheus.port != config.axon.port - ), "config.prometheus.port != config.axon.port" diff --git a/bittensor/_proto/__init__.py b/bittensor/_proto/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bittensor/_proto/bittensor.proto b/bittensor/_proto/bittensor.proto deleted file mode 100644 index 1bd758de79..0000000000 --- a/bittensor/_proto/bittensor.proto +++ /dev/null @@ -1,138 +0,0 @@ -// python3 -m grpc.tools.protoc bittensor/_proto/bittensor.proto -I. --python_out=. --grpc_python_out=. --proto_path bittensor/_proto/ -syntax = "proto3"; - -service TextPrompting { - rpc Forward (ForwardTextPromptingRequest) returns (ForwardTextPromptingResponse) {} - rpc Backward (BackwardTextPromptingRequest) returns (BackwardTextPromptingResponse) {} -} - -///////////////////////// -// TextPrompting // -///////////////////////// -message ForwardTextPromptingRequest { - int32 version = 1; - repeated string messages = 3; - float timeout = 4; -} -message ForwardTextPromptingResponse { - int32 version = 1; - string response = 3; - string return_message = 4; - ReturnCode return_code = 5; -} -message BackwardTextPromptingRequest { - int32 version = 1; - repeated float rewards = 3; - repeated string messages = 4; - string response = 5; - float timeout = 6; -} -message BackwardTextPromptingResponse { - int32 version = 1; - string return_message = 4; - ReturnCode return_code = 5; -} - - -// Return codes from Backward and Forward call. -enum ReturnCode { - NoReturn = 0; // Default Value - Success = 1; // Successful query. - Timeout = 2; // Request timeout. - Backoff = 3; // Call triggered a backoff. - Unavailable = 4; // Endpoint not available. - NotImplemented = 5; // Modality not implemented. - EmptyRequest = 6; // Request is empty. - EmptyResponse = 7; // Response is empty. - InvalidResponse = 8; // Request is invalid. - InvalidRequest = 9; // Response is invalid. - RequestShapeException = 10; // Request has invalid shape. - ResponseShapeException = 11; // Response has invalid shape. - RequestSerializationException = 12; // Request failed to serialize. - ResponseSerializationException = 13; // Response failed to serialize. - RequestDeserializationException = 14; // Request failed to deserialize. - ResponseDeserializationException = 15; // Response failed to deserialize. - NotServingNucleus = 16; // Receiving Neuron is not serving a Nucleus to query. - NucleusTimeout = 17; // Processing on the server side timeout. - NucleusFull = 18; // Returned when the processing queue on the server is full. - RequestIncompatibleVersion = 19; // The request handler is incompatible with the request version. - ResponseIncompatibleVersion = 20; // The request handler is incompatible with the request version. - SenderUnknown = 21; // The requester is not known by the receiver. - UnknownException = 22; // Unknown exception. - Unauthenticated = 23; // Authentication failed. - BadEndpoint = 24; // Dummy endpoint - Blacklisted = 25; // Blacklisted -} - -///////////////// -// TensorProto // -///////////////// -// A serialized tensor object created using the serializer class. -// SIZE: 32 bytes + variable buffer size. -message Tensor { - // Version: [REQUIRED] Strictly increasing protocol version identifier. - // Indentifies protocol version for backward compatibility. - // i.e. '0.1.5' = (100 * 0) + (10 * 1) + (1 * 5) = 15 - int32 version = 1; - - // Buffer: [REQUIRED] Serialized raw tensor content. - bytes buffer = 2; - - // Shape: [REQUIRED] Shape of this tensor. - // NOTE: Variable dimensions (i.e. batch) are non-explicit here as -1. - // ~ 5 * int32 = 128 bits - (16 bytes) - repeated int64 shape = 3; - - // Serializer: [REQUIRED] Specifies the serialization/deserialization method. - // Users should be able to decode all tensors by specifying the encoding type and the raw data. - // i.e. - // 1. (client) serializer = bittensor.bittensor.serializer_for_type(bittensor.Serializer.MSGPACK) - // 2. (client) serializer.serialize(torch.Tensor, from_type = bittensor.proto.TensorType.TORCH) --> bittensor.proto.Tensor - // 3. (server) deserializer = bittensor.bittensor.serializer_for_type(request.serialzer) - // 4. (server) deserializer.deserialize(request.tensor, to_type = bittensor.proto.TensorType.TENSORFLOW) --> tensorflow.Tensor - // SIZE: 32-bits (4 bytes) - Serializer serializer = 4; - - // TensorType: [REQUIRED] Purely a placeholder, not used in deserialization etc, - // however, could come in handy later - // SIZE: 32-bits (4 bytes) - TensorType tensor_type = 5; - - // Dtype: [REQUIRED] The tensor datatype. - // Used for serialization deserialization. - // int32 32-bits (4-bytes) - DataType dtype = 6; - - // Requires grad: [OPTIONAL] Does this tensor require a gradient. - // 1 bit. - bool requires_grad = 8; -} - -enum Serializer { - // PICKLE = 0; // PICKLE serializer (REMOVED for security reasons.) - MSGPACK = 0; // MSGPACK serializer - CMPPACK = 1; // CMPPACK serializer -} - -// TensorType: [REQUIRED] The tensor type, for use between multipl frameworks. -enum TensorType { - TORCH = 0; // Torch object - TENSORFLOW = 1; // Tensorflow tensor type. - NUMPY = 2; // Numpy tensor type. -} - -// Dtype: [REQUIRED] The tensor datatype. -// Used for serialization deserialization. -// int32 32-bits (4-bytes) -enum DataType { - UNKNOWN = 0; - FLOAT32 = 1; - FLOAT64 = 2; - INT32 = 3; - INT64 = 4; - UTF8 = 5; - FLOAT16 = 6; - BOOL = 7; -} - - diff --git a/bittensor/_proto/bittensor_pb2.py b/bittensor/_proto/bittensor_pb2.py deleted file mode 100644 index 1f25f12c94..0000000000 --- a/bittensor/_proto/bittensor_pb2.py +++ /dev/null @@ -1,1085 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: bittensor/_proto/bittensor.proto -"""Generated protocol buffer code.""" -from google.protobuf.internal import enum_type_wrapper -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from google.protobuf import reflection as _reflection -from google.protobuf import symbol_database as _symbol_database - -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -DESCRIPTOR = _descriptor.FileDescriptor( - name="bittensor/_proto/bittensor.proto", - package="", - syntax="proto3", - serialized_options=None, - create_key=_descriptor._internal_create_key, - serialized_pb=b'\n bittensor/_proto/bittensor.proto"Q\n\x1b\x46orwardTextPromptingRequest\x12\x0f\n\x07version\x18\x01 \x01(\x05\x12\x10\n\x08messages\x18\x03 \x03(\t\x12\x0f\n\x07timeout\x18\x04 \x01(\x02"{\n\x1c\x46orwardTextPromptingResponse\x12\x0f\n\x07version\x18\x01 \x01(\x05\x12\x10\n\x08response\x18\x03 \x01(\t\x12\x16\n\x0ereturn_message\x18\x04 \x01(\t\x12 \n\x0breturn_code\x18\x05 \x01(\x0e\x32\x0b.ReturnCode"u\n\x1c\x42\x61\x63kwardTextPromptingRequest\x12\x0f\n\x07version\x18\x01 \x01(\x05\x12\x0f\n\x07rewards\x18\x03 \x03(\x02\x12\x10\n\x08messages\x18\x04 \x03(\t\x12\x10\n\x08response\x18\x05 \x01(\t\x12\x0f\n\x07timeout\x18\x06 \x01(\x02"j\n\x1d\x42\x61\x63kwardTextPromptingResponse\x12\x0f\n\x07version\x18\x01 \x01(\x05\x12\x16\n\x0ereturn_message\x18\x04 \x01(\t\x12 \n\x0breturn_code\x18\x05 \x01(\x0e\x32\x0b.ReturnCode"\xac\x01\n\x06Tensor\x12\x0f\n\x07version\x18\x01 \x01(\x05\x12\x0e\n\x06\x62uffer\x18\x02 \x01(\x0c\x12\r\n\x05shape\x18\x03 \x03(\x03\x12\x1f\n\nserializer\x18\x04 \x01(\x0e\x32\x0b.Serializer\x12 \n\x0btensor_type\x18\x05 \x01(\x0e\x32\x0b.TensorType\x12\x18\n\x05\x64type\x18\x06 \x01(\x0e\x32\t.DataType\x12\x15\n\rrequires_grad\x18\x08 \x01(\x08*\xda\x04\n\nReturnCode\x12\x0c\n\x08NoReturn\x10\x00\x12\x0b\n\x07Success\x10\x01\x12\x0b\n\x07Timeout\x10\x02\x12\x0b\n\x07\x42\x61\x63koff\x10\x03\x12\x0f\n\x0bUnavailable\x10\x04\x12\x12\n\x0eNotImplemented\x10\x05\x12\x10\n\x0c\x45mptyRequest\x10\x06\x12\x11\n\rEmptyResponse\x10\x07\x12\x13\n\x0fInvalidResponse\x10\x08\x12\x12\n\x0eInvalidRequest\x10\t\x12\x19\n\x15RequestShapeException\x10\n\x12\x1a\n\x16ResponseShapeException\x10\x0b\x12!\n\x1dRequestSerializationException\x10\x0c\x12"\n\x1eResponseSerializationException\x10\r\x12#\n\x1fRequestDeserializationException\x10\x0e\x12$\n ResponseDeserializationException\x10\x0f\x12\x15\n\x11NotServingNucleus\x10\x10\x12\x12\n\x0eNucleusTimeout\x10\x11\x12\x0f\n\x0bNucleusFull\x10\x12\x12\x1e\n\x1aRequestIncompatibleVersion\x10\x13\x12\x1f\n\x1bResponseIncompatibleVersion\x10\x14\x12\x11\n\rSenderUnknown\x10\x15\x12\x14\n\x10UnknownException\x10\x16\x12\x13\n\x0fUnauthenticated\x10\x17\x12\x0f\n\x0b\x42\x61\x64\x45ndpoint\x10\x18\x12\x0f\n\x0b\x42lacklisted\x10\x19*&\n\nSerializer\x12\x0b\n\x07MSGPACK\x10\x00\x12\x0b\n\x07\x43MPPACK\x10\x01*2\n\nTensorType\x12\t\n\x05TORCH\x10\x00\x12\x0e\n\nTENSORFLOW\x10\x01\x12\t\n\x05NUMPY\x10\x02*h\n\x08\x44\x61taType\x12\x0b\n\x07UNKNOWN\x10\x00\x12\x0b\n\x07\x46LOAT32\x10\x01\x12\x0b\n\x07\x46LOAT64\x10\x02\x12\t\n\x05INT32\x10\x03\x12\t\n\x05INT64\x10\x04\x12\x08\n\x04UTF8\x10\x05\x12\x0b\n\x07\x46LOAT16\x10\x06\x12\x08\n\x04\x42OOL\x10\x07\x32\xa6\x01\n\rTextPrompting\x12H\n\x07\x46orward\x12\x1c.ForwardTextPromptingRequest\x1a\x1d.ForwardTextPromptingResponse"\x00\x12K\n\x08\x42\x61\x63kward\x12\x1d.BackwardTextPromptingRequest\x1a\x1e.BackwardTextPromptingResponse"\x00\x62\x06proto3', -) - -_RETURNCODE = _descriptor.EnumDescriptor( - name="ReturnCode", - full_name="ReturnCode", - filename=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - values=[ - _descriptor.EnumValueDescriptor( - name="NoReturn", - index=0, - number=0, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key, - ), - _descriptor.EnumValueDescriptor( - name="Success", - index=1, - number=1, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key, - ), - _descriptor.EnumValueDescriptor( - name="Timeout", - index=2, - number=2, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key, - ), - _descriptor.EnumValueDescriptor( - name="Backoff", - index=3, - number=3, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key, - ), - _descriptor.EnumValueDescriptor( - name="Unavailable", - index=4, - number=4, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key, - ), - _descriptor.EnumValueDescriptor( - name="NotImplemented", - index=5, - number=5, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key, - ), - _descriptor.EnumValueDescriptor( - name="EmptyRequest", - index=6, - number=6, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key, - ), - _descriptor.EnumValueDescriptor( - name="EmptyResponse", - index=7, - number=7, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key, - ), - _descriptor.EnumValueDescriptor( - name="InvalidResponse", - index=8, - number=8, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key, - ), - _descriptor.EnumValueDescriptor( - name="InvalidRequest", - index=9, - number=9, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key, - ), - _descriptor.EnumValueDescriptor( - name="RequestShapeException", - index=10, - number=10, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key, - ), - _descriptor.EnumValueDescriptor( - name="ResponseShapeException", - index=11, - number=11, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key, - ), - _descriptor.EnumValueDescriptor( - name="RequestSerializationException", - index=12, - number=12, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key, - ), - _descriptor.EnumValueDescriptor( - name="ResponseSerializationException", - index=13, - number=13, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key, - ), - _descriptor.EnumValueDescriptor( - name="RequestDeserializationException", - index=14, - number=14, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key, - ), - _descriptor.EnumValueDescriptor( - name="ResponseDeserializationException", - index=15, - number=15, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key, - ), - _descriptor.EnumValueDescriptor( - name="NotServingNucleus", - index=16, - number=16, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key, - ), - _descriptor.EnumValueDescriptor( - name="NucleusTimeout", - index=17, - number=17, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key, - ), - _descriptor.EnumValueDescriptor( - name="NucleusFull", - index=18, - number=18, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key, - ), - _descriptor.EnumValueDescriptor( - name="RequestIncompatibleVersion", - index=19, - number=19, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key, - ), - _descriptor.EnumValueDescriptor( - name="ResponseIncompatibleVersion", - index=20, - number=20, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key, - ), - _descriptor.EnumValueDescriptor( - name="SenderUnknown", - index=21, - number=21, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key, - ), - _descriptor.EnumValueDescriptor( - name="UnknownException", - index=22, - number=22, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key, - ), - _descriptor.EnumValueDescriptor( - name="Unauthenticated", - index=23, - number=23, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key, - ), - _descriptor.EnumValueDescriptor( - name="BadEndpoint", - index=24, - number=24, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key, - ), - _descriptor.EnumValueDescriptor( - name="Blacklisted", - index=25, - number=25, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key, - ), - ], - containing_type=None, - serialized_options=None, - serialized_start=647, - serialized_end=1249, -) -_sym_db.RegisterEnumDescriptor(_RETURNCODE) - -ReturnCode = enum_type_wrapper.EnumTypeWrapper(_RETURNCODE) -_SERIALIZER = _descriptor.EnumDescriptor( - name="Serializer", - full_name="Serializer", - filename=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - values=[ - _descriptor.EnumValueDescriptor( - name="MSGPACK", - index=0, - number=0, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key, - ), - _descriptor.EnumValueDescriptor( - name="CMPPACK", - index=1, - number=1, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key, - ), - ], - containing_type=None, - serialized_options=None, - serialized_start=1251, - serialized_end=1289, -) -_sym_db.RegisterEnumDescriptor(_SERIALIZER) - -Serializer = enum_type_wrapper.EnumTypeWrapper(_SERIALIZER) -_TENSORTYPE = _descriptor.EnumDescriptor( - name="TensorType", - full_name="TensorType", - filename=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - values=[ - _descriptor.EnumValueDescriptor( - name="TORCH", - index=0, - number=0, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key, - ), - _descriptor.EnumValueDescriptor( - name="TENSORFLOW", - index=1, - number=1, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key, - ), - _descriptor.EnumValueDescriptor( - name="NUMPY", - index=2, - number=2, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key, - ), - ], - containing_type=None, - serialized_options=None, - serialized_start=1291, - serialized_end=1341, -) -_sym_db.RegisterEnumDescriptor(_TENSORTYPE) - -TensorType = enum_type_wrapper.EnumTypeWrapper(_TENSORTYPE) -_DATATYPE = _descriptor.EnumDescriptor( - name="DataType", - full_name="DataType", - filename=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - values=[ - _descriptor.EnumValueDescriptor( - name="UNKNOWN", - index=0, - number=0, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key, - ), - _descriptor.EnumValueDescriptor( - name="FLOAT32", - index=1, - number=1, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key, - ), - _descriptor.EnumValueDescriptor( - name="FLOAT64", - index=2, - number=2, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key, - ), - _descriptor.EnumValueDescriptor( - name="INT32", - index=3, - number=3, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key, - ), - _descriptor.EnumValueDescriptor( - name="INT64", - index=4, - number=4, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key, - ), - _descriptor.EnumValueDescriptor( - name="UTF8", - index=5, - number=5, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key, - ), - _descriptor.EnumValueDescriptor( - name="FLOAT16", - index=6, - number=6, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key, - ), - _descriptor.EnumValueDescriptor( - name="BOOL", - index=7, - number=7, - serialized_options=None, - type=None, - create_key=_descriptor._internal_create_key, - ), - ], - containing_type=None, - serialized_options=None, - serialized_start=1343, - serialized_end=1447, -) -_sym_db.RegisterEnumDescriptor(_DATATYPE) - -DataType = enum_type_wrapper.EnumTypeWrapper(_DATATYPE) -NoReturn = 0 -Success = 1 -Timeout = 2 -Backoff = 3 -Unavailable = 4 -NotImplemented = 5 -EmptyRequest = 6 -EmptyResponse = 7 -InvalidResponse = 8 -InvalidRequest = 9 -RequestShapeException = 10 -ResponseShapeException = 11 -RequestSerializationException = 12 -ResponseSerializationException = 13 -RequestDeserializationException = 14 -ResponseDeserializationException = 15 -NotServingNucleus = 16 -NucleusTimeout = 17 -NucleusFull = 18 -RequestIncompatibleVersion = 19 -ResponseIncompatibleVersion = 20 -SenderUnknown = 21 -UnknownException = 22 -Unauthenticated = 23 -BadEndpoint = 24 -Blacklisted = 25 -MSGPACK = 0 -CMPPACK = 1 -TORCH = 0 -TENSORFLOW = 1 -NUMPY = 2 -UNKNOWN = 0 -FLOAT32 = 1 -FLOAT64 = 2 -INT32 = 3 -INT64 = 4 -UTF8 = 5 -FLOAT16 = 6 -BOOL = 7 - - -_FORWARDTEXTPROMPTINGREQUEST = _descriptor.Descriptor( - name="ForwardTextPromptingRequest", - full_name="ForwardTextPromptingRequest", - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name="version", - full_name="ForwardTextPromptingRequest.version", - index=0, - number=1, - type=5, - cpp_type=1, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - ), - _descriptor.FieldDescriptor( - name="messages", - full_name="ForwardTextPromptingRequest.messages", - index=1, - number=3, - type=9, - cpp_type=9, - label=3, - has_default_value=False, - default_value=[], - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - ), - _descriptor.FieldDescriptor( - name="timeout", - full_name="ForwardTextPromptingRequest.timeout", - index=2, - number=4, - type=2, - cpp_type=6, - label=1, - has_default_value=False, - default_value=float(0), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=36, - serialized_end=117, -) - - -_FORWARDTEXTPROMPTINGRESPONSE = _descriptor.Descriptor( - name="ForwardTextPromptingResponse", - full_name="ForwardTextPromptingResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name="version", - full_name="ForwardTextPromptingResponse.version", - index=0, - number=1, - type=5, - cpp_type=1, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - ), - _descriptor.FieldDescriptor( - name="response", - full_name="ForwardTextPromptingResponse.response", - index=1, - number=3, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=b"".decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - ), - _descriptor.FieldDescriptor( - name="return_message", - full_name="ForwardTextPromptingResponse.return_message", - index=2, - number=4, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=b"".decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - ), - _descriptor.FieldDescriptor( - name="return_code", - full_name="ForwardTextPromptingResponse.return_code", - index=3, - number=5, - type=14, - cpp_type=8, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=119, - serialized_end=242, -) - - -_BACKWARDTEXTPROMPTINGREQUEST = _descriptor.Descriptor( - name="BackwardTextPromptingRequest", - full_name="BackwardTextPromptingRequest", - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name="version", - full_name="BackwardTextPromptingRequest.version", - index=0, - number=1, - type=5, - cpp_type=1, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - ), - _descriptor.FieldDescriptor( - name="rewards", - full_name="BackwardTextPromptingRequest.rewards", - index=1, - number=3, - type=2, - cpp_type=6, - label=3, - has_default_value=False, - default_value=[], - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - ), - _descriptor.FieldDescriptor( - name="messages", - full_name="BackwardTextPromptingRequest.messages", - index=2, - number=4, - type=9, - cpp_type=9, - label=3, - has_default_value=False, - default_value=[], - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - ), - _descriptor.FieldDescriptor( - name="response", - full_name="BackwardTextPromptingRequest.response", - index=3, - number=5, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=b"".decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - ), - _descriptor.FieldDescriptor( - name="timeout", - full_name="BackwardTextPromptingRequest.timeout", - index=4, - number=6, - type=2, - cpp_type=6, - label=1, - has_default_value=False, - default_value=float(0), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=244, - serialized_end=361, -) - - -_BACKWARDTEXTPROMPTINGRESPONSE = _descriptor.Descriptor( - name="BackwardTextPromptingResponse", - full_name="BackwardTextPromptingResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name="version", - full_name="BackwardTextPromptingResponse.version", - index=0, - number=1, - type=5, - cpp_type=1, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - ), - _descriptor.FieldDescriptor( - name="return_message", - full_name="BackwardTextPromptingResponse.return_message", - index=1, - number=4, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=b"".decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - ), - _descriptor.FieldDescriptor( - name="return_code", - full_name="BackwardTextPromptingResponse.return_code", - index=2, - number=5, - type=14, - cpp_type=8, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=363, - serialized_end=469, -) - - -_TENSOR = _descriptor.Descriptor( - name="Tensor", - full_name="Tensor", - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name="version", - full_name="Tensor.version", - index=0, - number=1, - type=5, - cpp_type=1, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - ), - _descriptor.FieldDescriptor( - name="buffer", - full_name="Tensor.buffer", - index=1, - number=2, - type=12, - cpp_type=9, - label=1, - has_default_value=False, - default_value=b"", - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - ), - _descriptor.FieldDescriptor( - name="shape", - full_name="Tensor.shape", - index=2, - number=3, - type=3, - cpp_type=2, - label=3, - has_default_value=False, - default_value=[], - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - ), - _descriptor.FieldDescriptor( - name="serializer", - full_name="Tensor.serializer", - index=3, - number=4, - type=14, - cpp_type=8, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - ), - _descriptor.FieldDescriptor( - name="tensor_type", - full_name="Tensor.tensor_type", - index=4, - number=5, - type=14, - cpp_type=8, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - ), - _descriptor.FieldDescriptor( - name="dtype", - full_name="Tensor.dtype", - index=5, - number=6, - type=14, - cpp_type=8, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - ), - _descriptor.FieldDescriptor( - name="requires_grad", - full_name="Tensor.requires_grad", - index=6, - number=8, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=472, - serialized_end=644, -) - -_FORWARDTEXTPROMPTINGRESPONSE.fields_by_name["return_code"].enum_type = _RETURNCODE -_BACKWARDTEXTPROMPTINGRESPONSE.fields_by_name["return_code"].enum_type = _RETURNCODE -_TENSOR.fields_by_name["serializer"].enum_type = _SERIALIZER -_TENSOR.fields_by_name["tensor_type"].enum_type = _TENSORTYPE -_TENSOR.fields_by_name["dtype"].enum_type = _DATATYPE -DESCRIPTOR.message_types_by_name[ - "ForwardTextPromptingRequest" -] = _FORWARDTEXTPROMPTINGREQUEST -DESCRIPTOR.message_types_by_name[ - "ForwardTextPromptingResponse" -] = _FORWARDTEXTPROMPTINGRESPONSE -DESCRIPTOR.message_types_by_name[ - "BackwardTextPromptingRequest" -] = _BACKWARDTEXTPROMPTINGREQUEST -DESCRIPTOR.message_types_by_name[ - "BackwardTextPromptingResponse" -] = _BACKWARDTEXTPROMPTINGRESPONSE -DESCRIPTOR.message_types_by_name["Tensor"] = _TENSOR -DESCRIPTOR.enum_types_by_name["ReturnCode"] = _RETURNCODE -DESCRIPTOR.enum_types_by_name["Serializer"] = _SERIALIZER -DESCRIPTOR.enum_types_by_name["TensorType"] = _TENSORTYPE -DESCRIPTOR.enum_types_by_name["DataType"] = _DATATYPE -_sym_db.RegisterFileDescriptor(DESCRIPTOR) - -ForwardTextPromptingRequest = _reflection.GeneratedProtocolMessageType( - "ForwardTextPromptingRequest", - (_message.Message,), - { - "DESCRIPTOR": _FORWARDTEXTPROMPTINGREQUEST, - "__module__": "bittensor._proto.bittensor_pb2" - # @@protoc_insertion_point(class_scope:ForwardTextPromptingRequest) - }, -) -_sym_db.RegisterMessage(ForwardTextPromptingRequest) - -ForwardTextPromptingResponse = _reflection.GeneratedProtocolMessageType( - "ForwardTextPromptingResponse", - (_message.Message,), - { - "DESCRIPTOR": _FORWARDTEXTPROMPTINGRESPONSE, - "__module__": "bittensor._proto.bittensor_pb2" - # @@protoc_insertion_point(class_scope:ForwardTextPromptingResponse) - }, -) -_sym_db.RegisterMessage(ForwardTextPromptingResponse) - -BackwardTextPromptingRequest = _reflection.GeneratedProtocolMessageType( - "BackwardTextPromptingRequest", - (_message.Message,), - { - "DESCRIPTOR": _BACKWARDTEXTPROMPTINGREQUEST, - "__module__": "bittensor._proto.bittensor_pb2" - # @@protoc_insertion_point(class_scope:BackwardTextPromptingRequest) - }, -) -_sym_db.RegisterMessage(BackwardTextPromptingRequest) - -BackwardTextPromptingResponse = _reflection.GeneratedProtocolMessageType( - "BackwardTextPromptingResponse", - (_message.Message,), - { - "DESCRIPTOR": _BACKWARDTEXTPROMPTINGRESPONSE, - "__module__": "bittensor._proto.bittensor_pb2" - # @@protoc_insertion_point(class_scope:BackwardTextPromptingResponse) - }, -) -_sym_db.RegisterMessage(BackwardTextPromptingResponse) - -Tensor = _reflection.GeneratedProtocolMessageType( - "Tensor", - (_message.Message,), - { - "DESCRIPTOR": _TENSOR, - "__module__": "bittensor._proto.bittensor_pb2" - # @@protoc_insertion_point(class_scope:Tensor) - }, -) -_sym_db.RegisterMessage(Tensor) - - -_TEXTPROMPTING = _descriptor.ServiceDescriptor( - name="TextPrompting", - full_name="TextPrompting", - file=DESCRIPTOR, - index=0, - serialized_options=None, - create_key=_descriptor._internal_create_key, - serialized_start=1450, - serialized_end=1616, - methods=[ - _descriptor.MethodDescriptor( - name="Forward", - full_name="TextPrompting.Forward", - index=0, - containing_service=None, - input_type=_FORWARDTEXTPROMPTINGREQUEST, - output_type=_FORWARDTEXTPROMPTINGRESPONSE, - serialized_options=None, - create_key=_descriptor._internal_create_key, - ), - _descriptor.MethodDescriptor( - name="Backward", - full_name="TextPrompting.Backward", - index=1, - containing_service=None, - input_type=_BACKWARDTEXTPROMPTINGREQUEST, - output_type=_BACKWARDTEXTPROMPTINGRESPONSE, - serialized_options=None, - create_key=_descriptor._internal_create_key, - ), - ], -) -_sym_db.RegisterServiceDescriptor(_TEXTPROMPTING) - -DESCRIPTOR.services_by_name["TextPrompting"] = _TEXTPROMPTING - -# @@protoc_insertion_point(module_scope) diff --git a/bittensor/_proto/bittensor_pb2_grpc.py b/bittensor/_proto/bittensor_pb2_grpc.py deleted file mode 100644 index badcb2bcd6..0000000000 --- a/bittensor/_proto/bittensor_pb2_grpc.py +++ /dev/null @@ -1,124 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc - -from bittensor._proto import bittensor_pb2 as bittensor_dot___proto_dot_bittensor__pb2 - - -class TextPromptingStub(object): - """Missing associated documentation comment in .proto file.""" - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.Forward = channel.unary_unary( - "/TextPrompting/Forward", - request_serializer=bittensor_dot___proto_dot_bittensor__pb2.ForwardTextPromptingRequest.SerializeToString, - response_deserializer=bittensor_dot___proto_dot_bittensor__pb2.ForwardTextPromptingResponse.FromString, - ) - self.Backward = channel.unary_unary( - "/TextPrompting/Backward", - request_serializer=bittensor_dot___proto_dot_bittensor__pb2.BackwardTextPromptingRequest.SerializeToString, - response_deserializer=bittensor_dot___proto_dot_bittensor__pb2.BackwardTextPromptingResponse.FromString, - ) - - -class TextPromptingServicer(object): - """Missing associated documentation comment in .proto file.""" - - def Forward(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details("Method not implemented!") - raise NotImplementedError("Method not implemented!") - - def Backward(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details("Method not implemented!") - raise NotImplementedError("Method not implemented!") - - -def add_TextPromptingServicer_to_server(servicer, server): - rpc_method_handlers = { - "Forward": grpc.unary_unary_rpc_method_handler( - servicer.Forward, - request_deserializer=bittensor_dot___proto_dot_bittensor__pb2.ForwardTextPromptingRequest.FromString, - response_serializer=bittensor_dot___proto_dot_bittensor__pb2.ForwardTextPromptingResponse.SerializeToString, - ), - "Backward": grpc.unary_unary_rpc_method_handler( - servicer.Backward, - request_deserializer=bittensor_dot___proto_dot_bittensor__pb2.BackwardTextPromptingRequest.FromString, - response_serializer=bittensor_dot___proto_dot_bittensor__pb2.BackwardTextPromptingResponse.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - "TextPrompting", rpc_method_handlers - ) - server.add_generic_rpc_handlers((generic_handler,)) - - -# This class is part of an EXPERIMENTAL API. -class TextPrompting(object): - """Missing associated documentation comment in .proto file.""" - - @staticmethod - def Forward( - request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None, - ): - return grpc.experimental.unary_unary( - request, - target, - "/TextPrompting/Forward", - bittensor_dot___proto_dot_bittensor__pb2.ForwardTextPromptingRequest.SerializeToString, - bittensor_dot___proto_dot_bittensor__pb2.ForwardTextPromptingResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - ) - - @staticmethod - def Backward( - request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None, - ): - return grpc.experimental.unary_unary( - request, - target, - "/TextPrompting/Backward", - bittensor_dot___proto_dot_bittensor__pb2.BackwardTextPromptingRequest.SerializeToString, - bittensor_dot___proto_dot_bittensor__pb2.BackwardTextPromptingResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - ) diff --git a/bittensor/_receptor/receptor_impl.py b/bittensor/_receptor/receptor_impl.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bittensor/_serializer/__init__.py b/bittensor/_serializer/__init__.py deleted file mode 100644 index 1a3472162a..0000000000 --- a/bittensor/_serializer/__init__.py +++ /dev/null @@ -1,153 +0,0 @@ -""" An interface for serializing and deserializing bittensor tensors""" - -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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 numpy as np -import bittensor -from typing import Tuple, List, Union, Optional - -from . import serializer_impl - - -class serializer: - """An interface for serializing and deserializing bittensor tensors""" - - class SerializationException(Exception): - """Raised during serialization""" - - class DeserializationException(Exception): - """Raised during deserialization""" - - class NoSerializerForEnum(Exception): - """Raised if there is no serializer for the passed type""" - - class SerializationTypeNotImplementedException(Exception): - """Raised if serialization/deserialization is not implemented for the passed object type""" - - def __new__( - cls, - serializer_type: bittensor.proto.Serializer = bittensor.proto.Serializer.MSGPACK, - ) -> "bittensor.Serializer": - r"""Returns the correct serializer object for the passed Serializer enum. - - Args: - serializer_type (:obj:`bittensor.proto.Serializer`, `required`): - The serializer_type ENUM from bittensor.proto. - - Returns: - Serializer: (obj: `bittensor.Serializer`, `required`): - The bittensor serializer/deserialzer for the passed type. - - Raises: - NoSerializerForEnum: (Exception): - Raised if the passed there is no serialzier for the passed type. - """ - # WARNING: the pickle serializer is not safe. Should be removed in future verions. - # if serializer_type == bittensor.proto.Serializer.PICKLE: - # return PyTorchPickleSerializer() - if serializer_type == bittensor.proto.Serializer.MSGPACK: - return serializer_impl.MSGPackSerializer() - elif serializer_type == bittensor.proto.Serializer.CMPPACK: - return serializer_impl.CMPPackSerializer() - else: - raise bittensor.serializer.NoSerializerForEnum( - "No known serialzier for proto type {}".format(serializer_type) - ) - - @staticmethod - def torch_dtype_to_bittensor_dtype(tdtype): - """Translates between torch.dtypes and bittensor.dtypes. - - Args: - tdtype (torch.dtype): torch.dtype to translate. - - Returns: - dtype: (bittensor.dtype): translated bittensor.dtype. - """ - if tdtype == torch.float32: - dtype = bittensor.proto.DataType.FLOAT32 - elif tdtype == torch.float64: - dtype = bittensor.proto.DataType.FLOAT64 - elif tdtype == torch.int32: - dtype = bittensor.proto.DataType.INT32 - elif tdtype == torch.int64: - dtype = bittensor.proto.DataType.INT64 - elif tdtype == torch.float16: - dtype = bittensor.proto.DataType.FLOAT16 - elif tdtype == torch.bool: - dtype = bittensor.proto.DataType.BOOL - else: - dtype = bittensor.proto.DataType.UNKNOWN - return dtype - - @staticmethod - def bittensor_dtype_to_torch_dtype(bdtype): - """Translates between bittensor.dtype and torch.dtypes. - - Args: - bdtype (bittensor.dtype): bittensor.dtype to translate. - - Returns: - dtype: (torch.dtype): translated torch.dtype. - """ - if bdtype == bittensor.proto.DataType.FLOAT32: - dtype = torch.float32 - elif bdtype == bittensor.proto.DataType.FLOAT64: - dtype = torch.float64 - elif bdtype == bittensor.proto.DataType.INT32: - dtype = torch.int32 - elif bdtype == bittensor.proto.DataType.INT64: - dtype = torch.int64 - elif bdtype == bittensor.proto.DataType.FLOAT16: - dtype = torch.float16 - elif bdtype == bittensor.proto.DataType.BOOL: - dtype = torch.bool - else: - raise bittensor.serializer.DeserializationException( - "Unknown bittensor.Dtype or no equivalent torch.dtype for bittensor.dtype = {}".format( - bdtype - ) - ) - return dtype - - @staticmethod - def bittensor_dtype_np_dtype(bdtype): - """Translates between bittensor.dtype and np.dtypes. - - Args: - bdtype (bittensor.dtype): bittensor.dtype to translate. - - Returns: - dtype: (numpy.dtype): translated np.dtype. - """ - if bdtype == bittensor.proto.DataType.FLOAT32: - dtype = np.float32 - elif bdtype == bittensor.proto.DataType.FLOAT64: - dtype = np.float64 - elif bdtype == bittensor.proto.DataType.INT32: - dtype = np.int32 - elif bdtype == bittensor.proto.DataType.INT64: - dtype = np.int64 - else: - raise bittensor.serializer.SerializationException( - "Unknown bittensor.dtype or no equivalent numpy.dtype for bittensor.dtype = {}".format( - bdtype - ) - ) - return dtype diff --git a/bittensor/_serializer/serializer_impl.py b/bittensor/_serializer/serializer_impl.py deleted file mode 100644 index aee733bd0b..0000000000 --- a/bittensor/_serializer/serializer_impl.py +++ /dev/null @@ -1,256 +0,0 @@ -""" An interface for serializing and deserializing bittensor tensors""" - -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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 msgpack -import msgpack_numpy -from typing import Tuple, List, Union, Optional - - -import bittensor - - -class Serializer(object): - r"""Bittensor base serialization object for converting between bittensor.proto.Tensor and their - various python tensor equivalents. i.e. torch.Tensor or tensorflow.Tensor - """ - - @staticmethod - def empty(): - """Returns an empty bittensor.proto.Tensor message with the version""" - torch_proto = bittensor.proto.Tensor(version=bittensor.__version_as_int__) - return torch_proto - - def serialize( - self, tensor_obj: object, from_type: int = bittensor.proto.TensorType.TORCH - ) -> bittensor.proto.Tensor: - """Serializes a torch object to bittensor.proto.Tensor wire format. - - Args: - tensor_obj (:obj:`object`, `required`): - general tensor object i.e. torch.Tensor or tensorflow.Tensor - - from_type (`obj`: bittensor.proto.TensorType, `Optional`): - Serialization from this type. i.e. bittensor.proto.TensorType.TORCH or bittensor.proto.TensorType.TENSORFLOW - - Returns: - tensor_pb2: (obj: `bittensor.proto.Tensor`, `Optional`): - Serialized tensor as bittensor.proto.proto. - - Raises: - SerializationTypeNotImplementedException (Exception): - Raised if the serializer does not implement the conversion between the passed type and a bittensor.proto.Tensor - - SerializationException: (Exception): - Raised when the subclass serialization throws an error for the passed object. - """ - # TODO (const): add deserialization types for torch -> tensorflow - if from_type == bittensor.proto.TensorType.TORCH: - return self.serialize_from_torch(torch_tensor=tensor_obj) - - elif from_type == bittensor.proto.TensorType.NUMPY: - return self.serialize_from_numpy(numpy_tensor=tensor_obj) - - elif from_type == bittensor.proto.TensorType.TENSORFLOW: - return self.serialize_from_tensorflow(tensorflow_tensor=tensor_obj) - - else: - raise bittensor.serializer.SerializationTypeNotImplementedException( - "Serialization from type {} not implemented.".format(from_type) - ) - - raise NotImplementedError - - def deserialize( - self, - tensor_pb2: bittensor.proto.Tensor, - to_type: int = bittensor.proto.TensorType.TORCH, - ) -> object: - """Serializes a torch object to bittensor.proto.Tensor wire format. - - Args: - tensor_pb2 (`obj`: bittensor.proto.Tensor, `required`): - Serialized tensor as bittensor.proto.proto. - - to_type (`obj`: bittensor.proto.TensorType, `required`): - Deserialization to this type. i.e. bittensor.proto.TensorType.TORCH or bittensor.proto.TensorType.TENSORFLOW - - Returns: - tensor_obj (:obj:`torch.FloatTensor`, `required`): - tensor object of type from_type in bittensor.proto.TensorType - - Raises: - SerializationTypeNotImplementedException (Exception): - Raised if the serializer does not implement the conversion between the pb2 and the passed type. - - DeserializationException: (Exception): - Raised when the subclass deserializer throws an error for the passed object. - """ - # TODO (const): add deserialization types for torch -> tensorflow - if to_type == bittensor.proto.TensorType.TORCH: - return self.deserialize_to_torch(tensor_pb2) - - elif to_type == bittensor.proto.TensorType.NUMPY: - return self.deserialize_to_numpy(tensor_pb2) - - elif to_type == bittensor.proto.TensorType.TENSORFLOW: - return self.deserialize_to_tensorflow(tensor_pb2) - - else: - raise bittensor.serializer.SerializationTypeNotImplementedException( - "Deserialization to type {} not implemented.".format(to_type) - ) - - def serialize_from_tensorflow( - self, tensorflow_tensor: torch.Tensor - ) -> bittensor.proto.Tensor: - """tensorflow -> bittensor.proto.Tensor""" - raise bittensor.serializer.SerializationTypeNotImplementedException - - def serialize_from_torch( - self, torch_tensor: torch.Tensor - ) -> bittensor.proto.Tensor: - """torch -> bittensor.proto.Tensor""" - raise bittensor.serializer.SerializationTypeNotImplementedException - - def serialize_from_numpy( - self, numpy_tensor: torch.Tensor - ) -> bittensor.proto.Tensor: - """numpy -> bittensor.proto.Tensor""" - raise bittensor.serializer.SerializationTypeNotImplementedException - - def deserialize_to_torch(self, tensor_pb2: bittensor.proto.Tensor) -> torch.Tensor: - """bittensor.proto.Tensor -> torch""" - raise bittensor.serializer.SerializationTypeNotImplementedException - - def deserialize_to_tensorflow(self, tensor_pb2: bittensor.proto.Tensor) -> object: - """bittensor.proto.Tensor -> tensorflow""" - raise bittensor.serializer.SerializationTypeNotImplementedException - - def deserialize_to_numpy(self, tensor_pb2: bittensor.proto.Tensor) -> object: - """bittensor.proto.Tensor -> numpy""" - raise bittensor.serializer.SerializationTypeNotImplementedException - - -class MSGPackSerializer(Serializer): - """Make conversion between torch and bittensor.proto.torch""" - - def serialize_from_torch( - self, torch_tensor: torch.Tensor - ) -> bittensor.proto.Tensor: - """Serializes a torch.Tensor to an bittensor Tensor proto. - - Args: - torch_tensor (torch.Tensor): - Torch tensor to serialize. - Returns: - bittensor.proto.Tensor: - The serialized torch tensor as bittensor.proto.proto. - """ - dtype = bittensor.serializer.torch_dtype_to_bittensor_dtype(torch_tensor.dtype) - shape = list(torch_tensor.shape) - torch_numpy = torch_tensor.cpu().detach().numpy().copy() - data_buffer = msgpack.packb(torch_numpy, default=msgpack_numpy.encode) - torch_proto = bittensor.proto.Tensor( - version=bittensor.__version_as_int__, - buffer=data_buffer, - shape=shape, - dtype=dtype, - serializer=bittensor.proto.Serializer.MSGPACK, - tensor_type=bittensor.proto.TensorType.TORCH, - requires_grad=torch_tensor.requires_grad, - ) - return torch_proto - - def deserialize_to_torch(self, torch_proto: bittensor.proto.Tensor) -> torch.Tensor: - """Deserializes an bittensor.proto.Tensor to a torch.Tensor object. - - Args: - torch_proto (bittensor.proto.Tensor): - Proto containing torch tensor to derserialize. - - Returns: - torch.Tensor: - Deserialized torch tensor. - """ - dtype = bittensor.serializer.bittensor_dtype_to_torch_dtype(torch_proto.dtype) - shape = tuple(torch_proto.shape) - numpy_object = msgpack.unpackb( - torch_proto.buffer, object_hook=msgpack_numpy.decode - ).copy() - torch_object = ( - torch.as_tensor(numpy_object) - .view(shape) - .requires_grad_(torch_proto.requires_grad) - ) - return torch_object.type(dtype) - - -class CMPPackSerializer(Serializer): - """Make conversion between torch and bittensor.proto.torch in float16""" - - def serialize_from_torch( - self, torch_tensor: torch.Tensor - ) -> bittensor.proto.Tensor: - """Serializes a torch.Tensor to an bittensor Tensor proto in float16 - - Args: - torch_tensor (torch.Tensor): - Torch tensor to serialize. - Returns: - bittensor.proto.Tensor: - The serialized torch tensor as bittensor.proto.proto. - """ - dtype = bittensor.serializer.torch_dtype_to_bittensor_dtype(torch_tensor.dtype) - shape = list(torch_tensor.shape) - torch_numpy = torch_tensor.cpu().detach().half().numpy().copy() - data_buffer = msgpack.packb(torch_numpy, default=msgpack_numpy.encode) - torch_proto = bittensor.proto.Tensor( - version=bittensor.__version_as_int__, - buffer=data_buffer, - shape=shape, - dtype=dtype, - serializer=bittensor.proto.Serializer.CMPPACK, - tensor_type=bittensor.proto.TensorType.TORCH, - requires_grad=torch_tensor.requires_grad, - ) - return torch_proto - - def deserialize_to_torch(self, torch_proto: bittensor.proto.Tensor) -> torch.Tensor: - """Deserializes an bittensor.proto.Tensor to a torch.Tensor object. - - Args: - torch_proto (bittensor.proto.Tensor): - Proto containing torch tensor to derserialize. - - Returns: - torch.Tensor: - Deserialized torch tensor. - """ - dtype = bittensor.serializer.bittensor_dtype_to_torch_dtype(torch_proto.dtype) - shape = tuple(torch_proto.shape) - numpy_object = msgpack.unpackb( - torch_proto.buffer, object_hook=msgpack_numpy.decode - ).copy() - torch_object = ( - torch.as_tensor(numpy_object) - .view(shape) - .requires_grad_(torch_proto.requires_grad) - ) - return torch_object.type(dtype) diff --git a/bittensor/_subtensor/__init__.py b/bittensor/_subtensor/__init__.py deleted file mode 100644 index 6cceb8e388..0000000000 --- a/bittensor/_subtensor/__init__.py +++ /dev/null @@ -1,360 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao -# Copyright © 2023 Opentensor Foundation - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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 os -import copy -import argparse -import bittensor - -from loguru import logger -from substrateinterface import SubstrateInterface -from torch.cuda import is_available as is_cuda_available -from . import subtensor_impl, subtensor_mock - -logger = logger.opt(colors=True) - -GLOBAL_SUBTENSOR_MOCK_PROCESS_NAME = "node-subtensor" - - -class subtensor: - """Factory Class for both bittensor.Subtensor and Mock_Subtensor Classes - - The Subtensor class handles interactions with the substrate subtensor chain. - By default, the Subtensor class connects to the Finney which serves as the main bittensor network. - """ - - def __new__( - cls, - config: "bittensor.config" = None, - network: str = None, - chain_endpoint: str = None, - _mock: bool = None, - ) -> "bittensor.Subtensor": - r"""Initializes a subtensor chain interface. - Args: - config (:obj:`bittensor.Config`, `optional`): - bittensor.subtensor.config() - network (default='local', type=str) - The subtensor network flag. The likely choices are: - -- local (local running network) - -- finney (main network) - -- mock (mock network for testing.) - If this option is set it overloads subtensor.chain_endpoint with - an entry point node from that network. - chain_endpoint (default=None, type=str) - The subtensor endpoint flag. If set, overrides the network argument. - _mock (bool, `optional`): - Returned object is mocks the underlying chain connection. - """ - if config == None: - config = subtensor.config() - config = copy.deepcopy(config) - - # Returns a mocked connection with a background chain connection. - config.subtensor._mock = _mock if _mock != None else config.subtensor._mock - if ( - config.subtensor._mock == True - or network == "mock" - or config.subtensor.get("network", bittensor.defaults.subtensor.network) - == "mock" - ): - config.subtensor._mock = True - return subtensor_mock.MockSubtensor() - - # Determine config.subtensor.chain_endpoint and config.subtensor.network config. - # If chain_endpoint is set, we override the network flag, otherwise, the chain_endpoint is assigned by the network. - # Argument importance: chain_endpoint > network > config.subtensor.chain_endpoint > config.subtensor.network - - # Select using chain_endpoint arg. - if chain_endpoint != None: - config.subtensor.chain_endpoint = chain_endpoint - if network != None: - config.subtensor.network = network - else: - config.subtensor.network = config.subtensor.get( - "network", bittensor.defaults.subtensor.network - ) - - # Select using network arg. - elif network != None: - config.subtensor.chain_endpoint = subtensor.determine_chain_endpoint( - network - ) - config.subtensor.network = network - - # Select using config.subtensor.chain_endpoint - elif config.subtensor.chain_endpoint != None: - config.subtensor.chain_endpoint = config.subtensor.chain_endpoint - config.subtensor.network = config.subtensor.get( - "network", bittensor.defaults.subtensor.network - ) - - # Select using config.subtensor.network - elif ( - config.subtensor.get("network", bittensor.defaults.subtensor.network) - != None - ): - config.subtensor.chain_endpoint = subtensor.determine_chain_endpoint( - config.subtensor.get("network", bittensor.defaults.subtensor.network) - ) - config.subtensor.network = config.subtensor.get( - "network", bittensor.defaults.subtensor.network - ) - - # Fallback to defaults. - else: - config.subtensor.chain_endpoint = subtensor.determine_chain_endpoint( - bittensor.defaults.subtensor.network - ) - config.subtensor.network = bittensor.defaults.subtensor.network - - # make sure it's wss:// or ws:// - # If it's bellagene (parachain testnet) then it has to be wss - endpoint_url: str = config.subtensor.chain_endpoint - - # make sure formatting is good - endpoint_url = bittensor.utils.networking.get_formatted_ws_endpoint_url( - endpoint_url - ) - - subtensor.check_config(config) - network = config.subtensor.get("network", bittensor.defaults.subtensor.network) - substrate = SubstrateInterface( - ss58_format=bittensor.__ss58_format__, - use_remote_preset=True, - url=endpoint_url, - type_registry=bittensor.__type_registry__, - ) - return subtensor_impl.Subtensor( - substrate=substrate, - network=config.subtensor.get( - "network", bittensor.defaults.subtensor.network - ), - chain_endpoint=config.subtensor.chain_endpoint, - ) - - @staticmethod - def config() -> "bittensor.Config": - parser = argparse.ArgumentParser() - subtensor.add_args(parser) - return bittensor.config(parser) - - @classmethod - def help(cls): - """Print help to stdout""" - parser = argparse.ArgumentParser() - cls.add_args(parser) - print(cls.__new__.__doc__) - parser.print_help() - - @classmethod - def add_args(cls, parser: argparse.ArgumentParser, prefix: str = None): - prefix_str = "" if prefix == None else prefix + "." - if prefix is not None: - if bittensor.defaults.get(prefix, d=None) == None: - setattr(bittensor.defaults, prefix, bittensor.Config()) - getattr(bittensor.defaults, prefix).subtensor = bittensor.defaults.subtensor - try: - parser.add_argument( - "--" + prefix_str + "subtensor.network", - default=bittensor.defaults.subtensor.network, - type=str, - help="""The subtensor network flag. The likely choices are: - -- finney (main network) - -- local (local running network) - -- mock (creates a mock connection (for testing)) - If this option is set it overloads subtensor.chain_endpoint with - an entry point node from that network. - """, - ) - parser.add_argument( - "--" + prefix_str + "subtensor.chain_endpoint", - default=bittensor.defaults.subtensor.chain_endpoint, - type=str, - help="""The subtensor endpoint flag. If set, overrides the --network flag. - """, - ) - parser.add_argument( - "--" + prefix_str + "subtensor._mock", - action="store_true", - help="To turn on subtensor mocking for testing purposes.", - default=bittensor.defaults.subtensor._mock, - ) - # registration args. Used for register and re-register and anything that calls register. - parser.add_argument( - "--" + prefix_str + "subtensor.register.num_processes", - "-n", - dest=prefix_str + "subtensor.register.num_processes", - help="Number of processors to use for registration", - type=int, - default=bittensor.defaults.subtensor.register.num_processes, - ) - parser.add_argument( - "--" + prefix_str + "subtensor.register.update_interval", - "--" + prefix_str + "subtensor.register.cuda.update_interval", - "--" + prefix_str + "cuda.update_interval", - "-u", - help="The number of nonces to process before checking for next block during registration", - type=int, - default=bittensor.defaults.subtensor.register.update_interval, - ) - parser.add_argument( - "--" + prefix_str + "subtensor.register.no_output_in_place", - "--" + prefix_str + "no_output_in_place", - dest="subtensor.register.output_in_place", - help="Whether to not ouput the registration statistics in-place. Set flag to disable output in-place.", - action="store_false", - required=False, - default=bittensor.defaults.subtensor.register.output_in_place, - ) - parser.add_argument( - "--" + prefix_str + "subtensor.register.verbose", - help="Whether to ouput the registration statistics verbosely.", - action="store_true", - required=False, - default=bittensor.defaults.subtensor.register.verbose, - ) - - ## Registration args for CUDA registration. - parser.add_argument( - "--" + prefix_str + "subtensor.register.cuda.use_cuda", - "--" + prefix_str + "cuda", - "--" + prefix_str + "cuda.use_cuda", - default=bittensor.defaults.subtensor.register.cuda.use_cuda, - help="""Set flag to use CUDA to register.""", - action="store_true", - required=False, - ) - parser.add_argument( - "--" + prefix_str + "subtensor.register.cuda.no_cuda", - "--" + prefix_str + "no_cuda", - "--" + prefix_str + "cuda.no_cuda", - dest=prefix_str + "subtensor.register.cuda.use_cuda", - default=not bittensor.defaults.subtensor.register.cuda.use_cuda, - help="""Set flag to not use CUDA for registration""", - action="store_false", - required=False, - ) - - parser.add_argument( - "--" + prefix_str + "subtensor.register.cuda.dev_id", - "--" + prefix_str + "cuda.dev_id", - type=int, - nargs="+", - default=bittensor.defaults.subtensor.register.cuda.dev_id, - help="""Set the CUDA device id(s). Goes by the order of speed. (i.e. 0 is the fastest).""", - required=False, - ) - parser.add_argument( - "--" + prefix_str + "subtensor.register.cuda.TPB", - "--" + prefix_str + "cuda.TPB", - type=int, - default=bittensor.defaults.subtensor.register.cuda.TPB, - help="""Set the number of Threads Per Block for CUDA.""", - required=False, - ) - - except argparse.ArgumentError: - # re-parsing arguments. - pass - - @classmethod - def add_defaults(cls, defaults): - """Adds parser defaults to object from enviroment variables.""" - defaults.subtensor = bittensor.Config() - defaults.subtensor.network = ( - os.getenv("BT_SUBTENSOR_NETWORK") - if os.getenv("BT_SUBTENSOR_NETWORK") != None - else "finney" - ) - defaults.subtensor.chain_endpoint = ( - os.getenv("BT_SUBTENSOR_CHAIN_ENDPOINT") - if os.getenv("BT_SUBTENSOR_CHAIN_ENDPOINT") != None - else None - ) - defaults.subtensor._mock = ( - os.getenv("BT_SUBTENSOR_MOCK") - if os.getenv("BT_SUBTENSOR_MOCK") != None - else False - ) - - defaults.subtensor.register = bittensor.Config() - defaults.subtensor.register.num_processes = ( - os.getenv("BT_SUBTENSOR_REGISTER_NUM_PROCESSES") - if os.getenv("BT_SUBTENSOR_REGISTER_NUM_PROCESSES") != None - else None - ) # uses processor count by default within the function - defaults.subtensor.register.update_interval = ( - os.getenv("BT_SUBTENSOR_REGISTER_UPDATE_INTERVAL") - if os.getenv("BT_SUBTENSOR_REGISTER_UPDATE_INTERVAL") != None - else 50_000 - ) - defaults.subtensor.register.output_in_place = True - defaults.subtensor.register.verbose = False - - defaults.subtensor.register.cuda = bittensor.Config() - defaults.subtensor.register.cuda.dev_id = [0] - defaults.subtensor.register.cuda.use_cuda = False - defaults.subtensor.register.cuda.TPB = 256 - - @staticmethod - def check_config(config: "bittensor.Config"): - assert config.subtensor - # assert config.subtensor.network != None - if config.subtensor.get("register") and config.subtensor.register.get("cuda"): - assert all( - (isinstance(x, int) or isinstance(x, str) and x.isnumeric()) - for x in config.subtensor.register.cuda.get("dev_id", []) - ) - - if config.subtensor.register.cuda.get( - "use_cuda", bittensor.defaults.subtensor.register.cuda.use_cuda - ): - try: - import cubit - except ImportError: - raise ImportError( - "CUDA registration is enabled but cubit is not installed. Please install cubit." - ) - - if not is_cuda_available(): - raise RuntimeError( - "CUDA registration is enabled but no CUDA devices are detected." - ) - - @staticmethod - def determine_chain_endpoint(network: str): - if network == "finney": - # Kiru Finney stagin network. - return bittensor.__finney_entrypoint__ - elif network == "nobunaga": - # Staging network. - return bittensor.__nobunaga_entrypoint__ - elif network == "bellagene": - # Parachain test net - return bittensor.__bellagene_entrypoint__ - elif network == "local": - # Local chain. - return bittensor.__local_entrypoint__ - elif network == "mock": - return bittensor.__mock_entrypoint__ - elif network == "test": - return bittensor.__finney_test_entrypoint__ - else: - return bittensor.__local_entrypoint__ diff --git a/bittensor/_synapse/__init__.py b/bittensor/_synapse/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bittensor/_synapse/synapse.py b/bittensor/_synapse/synapse.py deleted file mode 100644 index fd637d6526..0000000000 --- a/bittensor/_synapse/synapse.py +++ /dev/null @@ -1,213 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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 grpc -import time -import torch -import asyncio -import bittensor - -from typing import Union, Optional, Callable, List, Dict, Tuple -from abc import ABC, abstractmethod -from dataclasses import dataclass - - -@dataclass -class SynapseCall(ABC): - """Base class for all synapse calls.""" - - is_forward: bool # If it is an forward of backward - name: str # The name of the call. - - def __init__( - self, - synapse: "bittensor.Synapse", - request_proto: object, - context: grpc.ServicerContext, - ): - metadata = dict(context.invocation_metadata()) - ( - _, - sender_hotkey, - _, - _, - ) = synapse.axon.auth_interceptor.parse_signature(metadata) - - self.completed = False - self.start_time = time.time() - self.timeout = request_proto.timeout - self.src_version = request_proto.version - self.src_hotkey = sender_hotkey - self.dest_hotkey = synapse.axon.wallet.hotkey.ss58_address - self.dest_version = bittensor.__version_as_int__ - self.return_code: bittensor.proto.ReturnCode = ( - bittensor.proto.ReturnCode.Success - ) - self.return_message: str = "Success" - self.priority: float = 0 - - def __repr__(self) -> str: - return f"SynapseCall( from: {self.src_hotkey}, forward: {self.is_forward}, start: {self.start_time}, timeout: {self.timeout}, priority: {self.priority}, completed: {self.completed})" - - def __str__(self) -> str: - return self.__repr__() - - @abstractmethod - def get_inputs_shape(self) -> torch.Size: - ... - - @abstractmethod - def get_outputs_shape(self) -> torch.Size: - ... - - @abstractmethod - def get_response_proto(self) -> object: - ... - - def _get_response_proto(self) -> object: - proto = self.get_response_proto() - proto.return_code = self.return_code - proto.return_message = self.return_message - return proto - - @abstractmethod - def apply(self): - ... - - def _apply(self): - # TODO(const): measure apply time. - self.apply() - - def end(self): - self.end_time = time.time() - self.elapsed = self.end_time - self.start_time - self.completed = True - - def log_outbound(self): - bittensor.logging.rpc_log( - axon=True, - forward=self.is_forward, - is_response=False, - code=self.return_code, - call_time=0, - pubkey=self.src_hotkey, - uid=None, - inputs=self.get_outputs_shape(), - outputs=self.get_outputs_shape(), - message=self.return_message, - synapse=self.name, - ) - - def log_inbound(self): - bittensor.logging.rpc_log( - axon=True, - forward=self.is_forward, - is_response=True, - code=self.return_code, - call_time=self.elapsed if self.completed else 0, - pubkey=self.src_hotkey, - uid=None, - inputs=self.get_inputs_shape(), - outputs=self.get_inputs_shape(), - message=self.return_message, - synapse=self.name, - ) - - -class Synapse(ABC): - name: str - - def __init__(self, axon: bittensor.axon): - self.axon = axon - - @abstractmethod - def blacklist(self, call: SynapseCall) -> Union[Tuple[bool, str], bool]: - ... - - def _blacklist(self, call: SynapseCall) -> Union[bool, str]: - blacklist = self.blacklist(call) - if isinstance(blacklist, tuple): - return blacklist - elif isinstance(blacklist, bool): - return blacklist, "no reason specified" - else: - raise ValueError( - "Blacklist response had type {} expected one of bool or Tuple[bool, str]".format( - blacklist - ) - ) - - @abstractmethod - def priority(self, call: SynapseCall) -> float: - ... - - def apply(self, call: SynapseCall) -> object: - bittensor.logging.trace("Synapse: {} received call: {}".format(self.name, call)) - try: - call.log_inbound() - - # Check blacklist. - blacklist, reason = self._blacklist(call) - if blacklist: - call.return_code = bittensor.proto.ReturnCode.Blacklisted - call.return_message = reason - bittensor.logging.info( - "Synapse: {} blacklisted call: {} reason: {}".format( - self.name, call, reason - ) - ) - - # Make call. - else: - # Queue the forward call with priority. - call.priority = self.priority(call) - future = self.axon.priority_threadpool.submit( - call._apply, - priority=call.priority, - ) - bittensor.logging.trace( - "Synapse: {} loaded future: {}".format(self.name, future) - ) - future.result(timeout=call.timeout) - bittensor.logging.trace( - "Synapse: {} completed call: {}".format(self.name, call) - ) - - # Catch timeouts - except asyncio.TimeoutError: - bittensor.logging.trace( - "Synapse: {} timeout: {}".format(self.name, call.timeout) - ) - call.return_code = bittensor.proto.ReturnCode.Timeout - call.return_message = "GRPC request timeout after: {}s".format(call.timeout) - - # Catch unknown exceptions. - except Exception as e: - bittensor.logging.trace( - "Synapse: {} unknown error: {}".format(self.name, str(e)) - ) - call.return_code = bittensor.proto.ReturnCode.UnknownException - call.return_message = str(e) - - # Finally return the call. - finally: - bittensor.logging.trace( - "Synapse: {} finalize call {}".format(self.name, call) - ) - call.end() - call.log_outbound() - return call._get_response_proto() diff --git a/bittensor/_synapse/text_prompting/__init__.py b/bittensor/_synapse/text_prompting/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bittensor/_synapse/text_prompting/synapse.py b/bittensor/_synapse/text_prompting/synapse.py deleted file mode 100644 index c199900bf7..0000000000 --- a/bittensor/_synapse/text_prompting/synapse.py +++ /dev/null @@ -1,132 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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 grpc -import torch -import bittensor - -from typing import List, Dict, Union, Callable -from abc import abstractmethod -import json - - -class SynapseForward(bittensor.SynapseCall): - name: str = "text_prompting_forward" - is_forward: bool = True - completion: str = "" - - def __init__( - self, - synapse: "TextPromptingSynapse", - request_proto: bittensor.proto.ForwardTextPromptingRequest, - forward_callback: Callable, - context: grpc.ServicerContext, - ): - super().__init__(synapse=synapse, request_proto=request_proto, context=context) - self.messages = request_proto.messages - self.formatted_messages = [json.loads(message) for message in self.messages] - self.forward_callback = forward_callback - - def apply(self): - bittensor.logging.trace("SynapseForward.apply()") - self.completion = self.forward_callback(messages=self.formatted_messages) - bittensor.logging.trace("SynapseForward.apply() = ", self.completion) - - def get_response_proto(self) -> bittensor.proto.ForwardTextPromptingResponse: - bittensor.logging.trace("SynapseForward.get_response_proto()") - return bittensor.ForwardTextPromptingResponse(response=self.completion) - - def get_inputs_shape(self) -> Union[torch.Size, None]: - bittensor.logging.trace("SynapseForward.get_inputs_shape()") - return torch.Size([len(message) for message in self.messages]) - - def get_outputs_shape(self) -> Union[torch.Size, None]: - bittensor.logging.trace("SynapseForward.get_outputs_shape()") - return torch.Size([len(self.completion)]) - - -class SynapseBackward(bittensor.SynapseCall): - name: str = "text_prompting_backward" - is_forward: bool = False - - def __init__( - self, - synapse: "TextPromptingSynapse", - request_proto: bittensor.proto.BackwardTextPromptingRequest, - backward_callback: Callable, - context: grpc.ServicerContext, - ): - super().__init__(synapse=synapse, request_proto=request_proto, context=context) - self.formatted_messages = [message for message in request_proto.messages] - self.formatted_rewards = torch.tensor( - [request_proto.rewards], dtype=torch.float32 - ) - self.completion = request_proto.response - self.backward_callback = backward_callback - - def apply(self): - self.backward_callback( - rewards=self.formatted_rewards, - messages=self.formatted_messages, - response=self.completion, - ) - - def get_response_proto(self) -> bittensor.proto.BackwardTextPromptingResponse: - return bittensor.BackwardTextPromptingResponse() - - def get_inputs_shape(self) -> torch.Size: - return torch.Size([len(message) for message in self.formatted_messages]) - - def get_outputs_shape(self) -> torch.Size: - return torch.Size([0]) - - -class TextPromptingSynapse(bittensor.Synapse, bittensor.grpc.TextPromptingServicer): - name: str = "text_prompting_synapse" - - def __init__(self, axon: "bittensor.axon"): - super().__init__(axon=axon) - self.axon = axon - bittensor.grpc.add_TextPromptingServicer_to_server(self, self.axon.server) - - @abstractmethod - def forward(self, messages: List[Dict[str, str]]) -> str: - ... - - @abstractmethod - def backward( - self, messages: List[Dict[str, str]], response: str, rewards: torch.FloatTensor - ) -> str: - ... - - def Forward( - self, - request: bittensor.proto.ForwardTextPromptingRequest, - context: grpc.ServicerContext, - ) -> bittensor.proto.ForwardTextPromptingResponse: - call = SynapseForward(self, request, self.forward, context) - bittensor.logging.trace("Forward: {} ".format(call)) - return self.apply(call=call) - - def Backward( - self, - request: bittensor.proto.BackwardTextPromptingRequest, - context: grpc.ServicerContext, - ) -> bittensor.proto.BackwardTextPromptingResponse: - call = SynapseBackward(self, request, self.backward, context) - bittensor.logging.trace("Backward: {}".format(call)) - return self.apply(call=call) diff --git a/bittensor/_threadpool/__init__.py b/bittensor/_threadpool/__init__.py deleted file mode 100644 index 0035681848..0000000000 --- a/bittensor/_threadpool/__init__.py +++ /dev/null @@ -1,126 +0,0 @@ -""" Factory method for creating priority threadpool -""" -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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 os -import argparse -import copy -import bittensor -from . import priority_thread_pool_impl - - -class prioritythreadpool: - """Factory method for creating priority threadpool""" - - def __new__( - cls, - config: "bittensor.config" = None, - max_workers: int = None, - maxsize: int = None, - ): - r"""Initializes a priority thread pool. - Args: - config (:obj:`bittensor.Config`, `optional`): - bittensor.subtensor.config() - max_workers (default=10, type=int) - . The maximum number of threads in thread pool - maxsize (default=-1, type=int) - The maximum number of tasks in the priority queue - """ - if config == None: - config = prioritythreadpool.config() - config = copy.deepcopy(config) - config.priority.max_workers = ( - max_workers if max_workers != None else config.priority.max_workers - ) - config.priority.maxsize = ( - maxsize if maxsize != None else config.priority.maxsize - ) - - prioritythreadpool.check_config(config) - return priority_thread_pool_impl.PriorityThreadPoolExecutor( - maxsize=config.priority.maxsize, max_workers=config.priority.max_workers - ) - - @classmethod - def add_args(cls, parser: argparse.ArgumentParser, prefix: str = None): - """Accept specific arguments from parser""" - prefix_str = "" if prefix == None else prefix + "." - if prefix is not None: - if bittensor.defaults.get(prefix, d=None) == None: - setattr(bittensor.defaults, prefix, bittensor.Config()) - getattr(bittensor.defaults, prefix).priority = bittensor.defaults.priority - try: - parser.add_argument( - "--" + prefix_str + "priority.max_workers", - type=int, - help="""maximum number of threads in thread pool""", - default=bittensor.defaults.priority.max_workers, - ) - parser.add_argument( - "--" + prefix_str + "priority.maxsize", - type=int, - help="""maximum size of tasks in priority queue""", - default=bittensor.defaults.priority.maxsize, - ) - except argparse.ArgumentError: - # re-parsing arguments. - pass - - @classmethod - def help(cls): - """Print help to stdout""" - parser = argparse.ArgumentParser() - cls.add_args(parser) - print(cls.__new__.__doc__) - parser.print_help() - - @classmethod - def add_defaults(cls, defaults): - """Adds parser defaults to object from enviroment variables.""" - defaults.priority = bittensor.Config() - defaults.priority = bittensor.Config() - defaults.priority.max_workers = ( - os.getenv("BT_PRIORITY_MAX_WORKERS") - if os.getenv("BT_PRIORITY_MAX_WORKERS") != None - else 5 - ) - defaults.priority.maxsize = ( - os.getenv("BT_PRIORITY_MAXSIZE") - if os.getenv("BT_PRIORITY_MAXSIZE") != None - else 10 - ) - - @classmethod - def config(cls) -> "bittensor.Config": - """Get config from the argument parser - Return: bittensor.config object - """ - parser = argparse.ArgumentParser() - prioritythreadpool.add_args(parser) - return bittensor.config(parser) - - @classmethod - def check_config(cls, config: "bittensor.Config"): - """Check config for threadpool worker number and size""" - assert isinstance( - config.priority.max_workers, int - ), "priority.max_workers must be a int" - assert isinstance( - config.priority.maxsize, int - ), "priority.maxsize must be a int" diff --git a/bittensor/_tokenizer/__init__.py b/bittensor/_tokenizer/__init__.py deleted file mode 100644 index 7cb9578a9d..0000000000 --- a/bittensor/_tokenizer/__init__.py +++ /dev/null @@ -1,49 +0,0 @@ -""" Implementation of the bittensor tokenizer -""" -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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. - -from transformers import AutoTokenizer -import bittensor -from bittensor.utils.tokenizer_utils import prep_tokenizer - - -class tokenizer: - """Implementation of the bittensor tokenizer""" - - cached_tokenizer_for_version: dict = {} - - def __new__(cls, version: str = None): - if version == None: - version = bittensor.__version__ - if version not in cls.cached_tokenizer_for_version: - _tokenizer = cls.get_tokenizer_for_version(version) - cls.cached_tokenizer_for_version[version] = _tokenizer - else: - _tokenizer = cls.cached_tokenizer_for_version[version] - return _tokenizer - - # Tokenizer - # NOTE (const): tokenizers are guaranteed to improve and expand as time progresses. We version the tokenizer here. - # neurons must be aware that versions will increase and be ready to convert between tokenizers. - # TODO (const): Add functionality to allow tokenizer conversion. i.e. for input token conversion. - @classmethod - def get_tokenizer_for_version(cls, version=bittensor.__version__): - """Return the GPT2 tokenizer with bittersor's special tokens""" - _tokenizer = AutoTokenizer.from_pretrained("gpt2", local_files_only=False) - _tokenizer = prep_tokenizer(_tokenizer) - return _tokenizer diff --git a/bittensor/axon.py b/bittensor/axon.py new file mode 100644 index 0000000000..c9fc220a2f --- /dev/null +++ b/bittensor/axon.py @@ -0,0 +1,1001 @@ +""" Create and init Axon, whcih services Forward and Backward requests from other neurons. +""" +# The MIT License (MIT) +# Copyright © 2021 Yuma Rao +# Copyright © 2022 Opentensor Foundation +# Copyright © 2023 Opentensor Technologies Inc + +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the “Software”), to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of +# the Software. + +# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# 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 os +import uuid +import copy +import json +import time +import base64 +import asyncio +import inspect +import uvicorn +import argparse +import traceback +import threading +import bittensor +import contextlib + +from inspect import signature, Signature, Parameter +from fastapi.responses import JSONResponse +from substrateinterface import Keypair +from fastapi import FastAPI, APIRouter, Request, Response, Depends +from starlette.types import Scope, Message +from starlette.responses import Response +from starlette.requests import Request +from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint +from typing import Dict, Optional, Tuple, Union, List, Callable, Any + + +""" FastAPI server that runs in a thread. +""" + + +class FastAPIThreadedServer(uvicorn.Server): + should_exit: bool = False + is_running: bool = False + + def install_signal_handlers(self): + pass + + @contextlib.contextmanager + def run_in_thread(self): + thread = threading.Thread(target=self.run, daemon=True) + thread.start() + try: + while not self.started: + time.sleep(1e-3) + yield + finally: + self.should_exit = True + thread.join() + + def _wrapper_run(self): + with self.run_in_thread(): + while not self.should_exit: + time.sleep(1e-3) + + def start(self): + if not self.is_running: + self.should_exit = False + thread = threading.Thread(target=self._wrapper_run, daemon=True) + thread.start() + self.is_running = True + + def stop(self): + if self.is_running: + self.should_exit = True + + +class axon: + """ + The `axon` class is an object that forms the core part of bittensor's serving synapses. + The class relies heavily on an underlying FastAPI router, which is utilized to create endpoints for different message types. + Methods in this class are equipped to deal with incoming requests from other scenarios in the network and serve as the server face for a neuron. + + It accepts multiple arguments, like wallet, configuration parameters, ip address, server binding port, external ip, external port and max workers. + Key methods involve managing and operating the FastAPI application router, including the attachment and operation of endpoints. + The `axon` class offers flexibility to specify custom rules to forward, blacklist, prioritize and verify incoming requests against custom functions. + + The class also encapsulates methods to add command-line arguments for user-friendly interaction with the program, and supports the handling of these arguments, + to define the behavior of the axon object. + + Internal mechanisms manage a thread pool to support concurrent requests handling, using defined priority levels. + + ### Example usage: + + ```python + import bittensor + + class MySyanpse( bittensor.Synapse ): + input: int = 1 + output: int = None + + # Define a custom request forwarding function + def forward( synapse: MySyanpse ) -> MySyanpse: + # Apply custom logic to synapse and return it + synapse.output = 2 + return synapse + + # Define a custom request verification function + def verify_my_synapse( synapse: MySyanpse ): + # Apply custom verification logic to synapse + # Optionally raise Exception + + # Define a custom request blacklist fucntion + def blacklist_my_synapse( synapse: MySyanpse ) -> bool: + # Apply custom blacklist + # return False ( if non blacklisted ) or True ( if blacklisted ) + + # Define a custom request priority fucntion + def prioritize_my_synape( synapse: MySyanpse ) -> float: + # Apply custom priority + return 1.0 + + # Initialize Axon object with a custom configuration + my_axon = bittensor.axon(config=my_config, wallet=my_wallet, port=9090, ip="192.0.2.0", external_ip="203.0.113.0", external_port=7070) + + # Attach the endpoint with the specified verification and forwarding functions + my_axon.attach( + forward_fn = forward_my_synapse, + verify_fn = verify_my_synapse, + blacklist_fn = blacklist_my_synapse, + priority_fn = prioritize_my_synape + ).attach( + forward_fn = forward_my_synapse_2, + verify_fn = verify_my_synapse_2, + blacklist_fn = blacklist_my_synapse_2, + priority_fn = prioritize_my_synape_2 + ).serve( + netuid = ... + subtensor = ... + ).start() + ``` + """ + + def info(self) -> "bittensor.AxonInfo": + """Returns the axon info object associated with this axon.""" + return bittensor.AxonInfo( + version=bittensor.__version_as_int__, + ip=self.external_ip, + ip_type=4, + port=self.external_port, + hotkey=self.wallet.hotkey.ss58_address, + coldkey=self.wallet.coldkeypub.ss58_address, + protocol=4, + placeholder1=0, + placeholder2=0, + ) + + def __init__( + self, + wallet: "bittensor.wallet" = None, + config: Optional["bittensor.config"] = None, + port: Optional[int] = None, + ip: Optional[str] = None, + external_ip: Optional[str] = None, + external_port: Optional[int] = None, + max_workers: Optional[int] = None, + ) -> "bittensor.axon": + r"""Creates a new bittensor.Axon object from passed arguments. + Args: + config (:obj:`Optional[bittensor.config]`, `optional`): + bittensor.axon.config() + wallet (:obj:`Optional[bittensor.wallet]`, `optional`): + bittensor wallet with hotkey and coldkeypub. + port (:type:`Optional[int]`, `optional`): + Binding port. + ip (:type:`Optional[str]`, `optional`): + Binding ip. + external_ip (:type:`Optional[str]`, `optional`): + The external ip of the server to broadcast to the network. + external_port (:type:`Optional[int]`, `optional`): + The external port of the server to broadcast to the network. + max_workers (:type:`Optional[int]`, `optional`): + Used to create the threadpool if not passed, specifies the number of active threads servicing requests. + """ + # Build and check config. + if config is None: + config = axon.config() + config = copy.deepcopy(config) + config.axon.ip = ip or config.axon.get("ip", bittensor.defaults.axon.ip) + config.axon.port = port or config.axon.get("port", bittensor.defaults.axon.port) + config.axon.external_ip = external_ip or config.axon.get( + "external_ip", bittensor.defaults.axon.external_ip + ) + config.axon.external_port = external_port or config.axon.get( + "external_port", bittensor.defaults.axon.external_port + ) + config.axon.max_workers = max_workers or config.axon.get( + "max_workers", bittensor.defaults.axon.max_workers + ) + axon.check_config(config) + self.config = config + + # Get wallet or use default. + self.wallet = wallet or bittensor.wallet() + + # Build axon objects. + self.uuid = str(uuid.uuid1()) + self.ip = self.config.axon.ip + self.port = self.config.axon.port + self.external_ip = ( + self.config.axon.external_ip + if self.config.axon.external_ip != None + else bittensor.utils.networking.get_external_ip() + ) + self.external_port = ( + self.config.axon.external_port + if self.config.axon.external_port != None + else self.config.axon.port + ) + self.full_address = str(self.config.axon.ip) + ":" + str(self.config.axon.port) + self.started = False + + # Build middleware + self.thread_pool = bittensor.PriorityThreadPoolExecutor( + max_workers=self.config.axon.max_workers + ) + self.nonces = {} + + # Request default functions. + self.forward_class_types = {} + self.blacklist_fns = {} + self.priority_fns = {} + self.forward_fns = {} + self.verify_fns = {} + self.required_hash_fields = {} + + # Instantiate FastAPI + self.app = FastAPI() + log_level = "trace" if bittensor.logging.__trace_on__ else "critical" + self.fast_config = uvicorn.Config( + self.app, host="0.0.0.0", port=self.config.axon.port, log_level=log_level + ) + self.fast_server = FastAPIThreadedServer(config=self.fast_config) + self.router = APIRouter() + self.app.include_router(self.router) + + # Build ourselves as the middleware. + self.app.add_middleware(AxonMiddleware, axon=self) + + # Attach default forward. + def ping(r: bittensor.Synapse) -> bittensor.Synapse: + return r + + self.attach( + forward_fn=ping, verify_fn=None, blacklist_fn=None, priority_fn=None + ) + + def attach( + self, + forward_fn: Callable, + blacklist_fn: Callable = None, + priority_fn: Callable = None, + verify_fn: Callable = None, + ) -> "bittensor.axon": + """ + Registers an API endpoint to the FastAPI application router. + It uses the name of the first argument of the 'forward_fn' function as the endpoint name. + + Args: + forward_fn (Callable): Function to be called when the API endpoint is accessed. + It should have at least one argument. + blacklist_fn (Callable, optional): Function to filter out undesired requests. It should take the same arguments + as 'forward_fn' and return a boolean value. Defaults to None, meaning no blacklist filter will be used. + priority_fn (Callable, optional): Function to rank requests based on their priority. It should take the same arguments + as 'forward_fn' and return a numerical value representing the request's priority. + Defaults to None, meaning no priority sorting will be applied. + verify_fn (Callable, optional): Function to verify requests. It should take the same arguments as 'forward_fn' and return + a boolean value. If None, 'self.default_verify' function will be used. + + Note: 'forward_fn', 'blacklist_fn', 'priority_fn', and 'verify_fn' should be designed to receive the same parameters. + + Raises: + AssertionError: If 'forward_fn' does not have the signature: forward( synapse: YourSynapse ) -> synapse: + AssertionError: If 'blacklist_fn' does not have the signature: blacklist( synapse: YourSynapse ) -> bool + AssertionError: If 'priority_fn' does not have the signature: priority( synapse: YourSynapse ) -> float + AssertionError: If 'verify_fn' does not have the signature: verify( synapse: YourSynapse ) -> None + + Returns: + self: Returns the instance of the AxonServer class for potential method chaining. + """ + + # Assert 'forward_fn' has exactly one argument + forward_sig = signature(forward_fn) + assert ( + len(list(forward_sig.parameters)) == 1 + ), "The passed function must have exactly one argument" + + # Obtain the class of the first argument of 'forward_fn' + request_class = forward_sig.parameters[ + list(forward_sig.parameters)[0] + ].annotation + + # Assert that the first argument of 'forward_fn' is a subclass of 'bittensor.Synapse' + assert issubclass( + request_class, bittensor.Synapse + ), "The argument of forward_fn must inherit from bittensor.Synapse" + + # Obtain the class name of the first argument of 'forward_fn' + request_name = forward_sig.parameters[ + list(forward_sig.parameters)[0] + ].annotation.__name__ + + # Add the endpoint to the router, making it available on both GET and POST methods + self.router.add_api_route( + f"/{request_name}", + forward_fn, + methods=["GET", "POST"], + dependencies=[Depends(self.verify_body_integrity)], + ) + self.app.include_router(self.router) + + # Expected signatures for 'blacklist_fn', 'priority_fn' and 'verify_fn' + blacklist_sig = Signature( + [ + Parameter( + "synapse", + Parameter.POSITIONAL_OR_KEYWORD, + annotation=forward_sig.parameters[ + list(forward_sig.parameters)[0] + ].annotation, + ) + ], + return_annotation=Tuple[bool, str], + ) + priority_sig = Signature( + [ + Parameter( + "synapse", + Parameter.POSITIONAL_OR_KEYWORD, + annotation=forward_sig.parameters[ + list(forward_sig.parameters)[0] + ].annotation, + ) + ], + return_annotation=float, + ) + verify_sig = Signature( + [ + Parameter( + "synapse", + Parameter.POSITIONAL_OR_KEYWORD, + annotation=forward_sig.parameters[ + list(forward_sig.parameters)[0] + ].annotation, + ) + ], + return_annotation=None, + ) + + # Check the signature of blacklist_fn, priority_fn and verify_fn if they are provided + if blacklist_fn: + assert ( + signature(blacklist_fn) == blacklist_sig + ), "The blacklist_fn function must have the signature: blacklist( synapse: {} ) -> Tuple[bool, str]".format( + request_name + ) + if priority_fn: + assert ( + signature(priority_fn) == priority_sig + ), "The priority_fn function must have the signature: priority( synapse: {} ) -> float".format( + request_name + ) + if verify_fn: + assert ( + signature(verify_fn) == verify_sig + ), "The verify_fn function must have the signature: verify( synapse: {} ) -> None".format( + request_name + ) + + # Store functions in appropriate attribute dictionaries + self.forward_class_types[request_name] = forward_sig.parameters[ + list(forward_sig.parameters)[0] + ].annotation + self.blacklist_fns[request_name] = blacklist_fn + self.priority_fns[request_name] = priority_fn + self.verify_fns[request_name] = ( + verify_fn or self.default_verify + ) # Use 'default_verify' if 'verify_fn' is None + self.forward_fns[request_name] = forward_fn + + # Parse required hash fields from the forward function protocol defaults + required_hash_fields = request_class.__dict__["__fields__"][ + "required_hash_fields" + ].default + self.required_hash_fields[request_name] = required_hash_fields + + return self + + @classmethod + def config(cls) -> "bittensor.config": + """ + Parses command-line arguments to form a bittensor configuration object. + + Returns: + bittensor.config: Configuration object with settings from command-line arguments. + """ + parser = argparse.ArgumentParser() + axon.add_args(parser) # Add specific axon-related arguments + return bittensor.config(parser, args=[]) + + @classmethod + def help(cls): + """ + Prints the help text (list of command-line arguments and their descriptions) to stdout. + """ + parser = argparse.ArgumentParser() + axon.add_args(parser) # Add specific axon-related arguments + print(cls.__new__.__doc__) # Print docstring of the class + parser.print_help() # Print parser's help text + + @classmethod + def add_args(cls, parser: argparse.ArgumentParser, prefix: str = None): + """ + Adds AxonServer-specific command-line arguments to the argument parser. + + Args: + parser (argparse.ArgumentParser): Argument parser to which the arguments will be added. + prefix (str, optional): Prefix to add to the argument names. Defaults to None. + + Note: + Environment variables are used to define default values for the arguments. + """ + prefix_str = "" if prefix is None else prefix + "." + try: + # Get default values from environment variables or use default values + default_axon_port = os.getenv("BT_AXON_PORT") or 8091 + default_axon_ip = os.getenv("BT_AXON_IP") or "[::]" + default_axon_external_port = os.getenv("BT_AXON_EXTERNAL_PORT") or None + default_axon_external_ip = os.getenv("BT_AXON_EXTERNAL_IP") or None + default_axon_max_workers = os.getenv("BT_AXON_MAX_WORERS") or 10 + + # Add command-line arguments to the parser + parser.add_argument( + "--" + prefix_str + "axon.port", + type=int, + help="The local port this axon endpoint is bound to. i.e. 8091", + default=default_axon_port, + ) + parser.add_argument( + "--" + prefix_str + "axon.ip", + type=str, + help="""The local ip this axon binds to. ie. [::]""", + default=default_axon_ip, + ) + parser.add_argument( + "--" + prefix_str + "axon.external_port", + type=int, + required=False, + help="""The public port this axon broadcasts to the network. i.e. 8091""", + default=default_axon_external_port, + ) + parser.add_argument( + "--" + prefix_str + "axon.external_ip", + type=str, + required=False, + help="""The external ip this axon broadcasts to the network to. ie. [::]""", + default=default_axon_external_ip, + ) + parser.add_argument( + "--" + prefix_str + "axon.max_workers", + type=int, + help="""The maximum number connection handler threads working simultaneously on this endpoint. + The grpc server distributes new worker threads to service requests up to this number.""", + default=default_axon_max_workers, + ) + + except argparse.ArgumentError: + # Exception handling for re-parsing arguments + pass + + async def verify_body_integrity(self, request: Request): + """ + Asynchronously verifies the integrity of the body of a request by comparing the hash of required fields + with the corresponding hashes provided in the request headers. This method is critical for ensuring + that the incoming request payload has not been altered or tampered with during transmission, establishing + a level of trust and security between the sender and receiver in the network. + + Args: + request (Request): The incoming FastAPI request object containing both headers and the request body. + + Returns: + dict: Returns the parsed body of the request as a dictionary if all the hash comparisons match, + indicating that the body is intact and has not been tampered with. + + Raises: + JSONResponse: Raises a JSONResponse with a 400 status code if any of the hash comparisons fail, + indicating a potential integrity issue with the incoming request payload. + The response includes the detailed error message specifying which field has a hash mismatch. + + Example: + Assuming this method is set as a dependency in a route: + + @app.post("/some_endpoint") + async def some_endpoint(body_dict: dict = Depends(verify_body_integrity)): + # body_dict is the parsed body of the request and is available for use in the route function. + # The function only executes if the body integrity verification is successful. + ... + """ + # Await and load the request body so we can inspect it + body = await request.body() + request_body = body.decode() if isinstance(body, bytes) else body + + # Gather the required field names from the axon's required_hash_fields dict + request_name = request.url.path.split("/")[1] + required_hash_fields = self.required_hash_fields[request_name] + + # Load the body dict and check if all required field hashes match + body_dict = json.loads(request_body) + field_hashes = [] + for required_field in required_hash_fields: + # Hash the field in the body to compare against the header hashes + body_value = body_dict.get(required_field, None) + if body_value == None: + raise ValueError(f"Missing required field {required_field}") + field_hash = bittensor.utils.hash(str(body_value)) + field_hashes.append(field_hash) + + parsed_body_hash = bittensor.utils.hash("".join(field_hashes)) + body_hash = request.headers.get("computed_body_hash", "") + if parsed_body_hash != body_hash: + raise ValueError( + f"Hash mismatch between header body hash {body_hash} and parsed body hash {parsed_body_hash}" + ) + + # If body is good, return the parsed body so that it can be passed onto the route function + return body_dict + + @classmethod + def check_config(cls, config: "bittensor.config"): + """ + This method checks the configuration for the axon's port and wallet. + + Args: + config (bittensor.config): The config object holding axon settings. + + Raises: + AssertionError: If the axon or external ports are not in range [1024, 65535] + """ + assert ( + config.axon.port > 1024 and config.axon.port < 65535 + ), "Axon port must be in range [1024, 65535]" + + assert config.axon.external_port is None or ( + config.axon.external_port > 1024 and config.axon.external_port < 65535 + ), "External port must be in range [1024, 65535]" + + def __str__(self) -> str: + """ + Provides a human-readable representation of the Axon instance. + """ + return "Axon({}, {}, {}, {}, {})".format( + self.ip, + self.port, + self.wallet.hotkey.ss58_address, + "started" if self.started else "stopped", + list(self.forward_fns.keys()), + ) + + def __repr__(self) -> str: + """ + Provides a machine-readable (unambiguous) representation of the Axon instance. + It is made identical to __str__ in this case. + """ + return self.__str__() + + def __del__(self): + """ + This magic method is called when the Axon object is about to be destroyed. + It ensures that the Axon server shuts down properly. + """ + self.stop() + + def start(self) -> "bittensor.axon": + """ + Starts the Axon server's GRPC server thread and marks the Axon as started. + + Returns: + bittensor.axon: The started Axon instance. + """ + self.fast_server.start() + self.started = True + return self + + def stop(self) -> "bittensor.axon": + """ + Stops the Axon server's GRPC server thread and marks the Axon as stopped. + + Returns: + bittensor.axon: The stopped Axon instance. + """ + self.fast_server.stop() + self.started = False + return self + + def serve( + self, netuid: int, subtensor: bittensor.subtensor = None + ) -> "bittensor.axon": + """ + Serves the axon on the passed subtensor connection using the axon wallet. + + Args: + netuid: int + The subnet uid to register on. + subtensor: Optional[ bittensor.Subtensor ] + The subtensor connection to use for serving. + Returns: + bittensor.axon: The served Axon instance. + """ + if subtensor == None: + subtensor = bittensor.subtensor() + subtensor.serve_axon(netuid=netuid, axon=self) + return self + + async def default_verify(self, synapse: bittensor.Synapse): + """ + This method is used to verify the authenticity of a received message using a digital signature. + It ensures that the message was not tampered with and was sent by the expected sender. + + Args: + synapse: bittensor.Synapse + bittensor request synapse. + + Raises: + Exception: If the receiver_hotkey doesn't match with self.receiver_hotkey. + Exception: If the nonce is not larger than the previous nonce for the same endpoint key. + Exception: If the signature verification fails. + + After successful verification, the nonce for the given endpoint key is updated. + + Note: + The verification process assumes the use of an asymmetric encryption algorithm, + where the sender signs the message with their private key and the receiver verifies the signature using the sender's public key. + """ + # Build the keypair from the dendrite_hotkey + keypair = Keypair(ss58_address=synapse.dendrite.hotkey) + + # Build the signature messages. + message = f"{synapse.dendrite.nonce}.{synapse.dendrite.hotkey}.{self.wallet.hotkey.ss58_address}.{synapse.dendrite.uuid}.{synapse.computed_body_hash}" + + # Build the unique endpoint key. + endpoint_key = f"{synapse.dendrite.hotkey}:{synapse.dendrite.uuid}" + + # Check the nonce from the endpoint key. + if endpoint_key in self.nonces.keys(): + # Ensure the nonce increases. + if synapse.dendrite.nonce <= self.nonces[endpoint_key]: + raise Exception("Nonce is too small") + + if not keypair.verify(message, synapse.dendrite.signature): + raise Exception( + f"Signature mismatch with {message} and {synapse.dendrite.signature}" + ) + + # Success + self.nonces[endpoint_key] = synapse.dendrite.nonce + + +class AxonMiddleware(BaseHTTPMiddleware): + """ + Class AxonMiddleware handles the entire process of the request to the Axon. + It fills the necessary information into the synapse and manages the logging of messages and errors. + It handles the verification, blacklist checking and running priority functions. + This class also runs the requested function and updates the headers of the response. + """ + + def __init__(self, app: "AxonMiddleware", axon: "bittensor.axon"): + """ + Initialize the AxonMiddleware class. + + Args: + app (object): An instance of the application where the middleware processor is used. + axon (object): The axon instance used to process the requests. + """ + super().__init__(app) + self.axon = axon + + async def dispatch( + self, request: Request, call_next: RequestResponseEndpoint + ) -> Request: + """ + Processes incoming requests. + + Args: + request(starlette: Request): The incoming request. + call_next(starlette: RequestResponseEndpoint): The function to call after processing the request. + + Returns: + response (starlette: Response): The processed request. + """ + # Records the start time of the request processing. + start_time = time.time() + + try: + # Set up the synapse from its headers. + synapse: bittensor.Synapse = await self.preprocess(request) + + # Logs the start of the request processing + bittensor.logging.debug( + f"axon | <-- | {request.headers.get('content-length', -1)} B | {synapse.name} | {synapse.dendrite.hotkey} | {synapse.dendrite.ip}:{synapse.dendrite.port} | 200 | Success " + ) + + # Call the blacklist function + await self.blacklist(synapse) + + # Call verify and return the verified request + await self.verify(synapse) + + # Call the priority function + await self.priority(synapse) + + # Call the run function + response = await self.run(synapse, call_next, request) + + # Call the postprocess function + response = await self.postprocess(synapse, response, start_time) + + # Start of catching all exceptions, updating the status message, and processing time. + except Exception as e: + # Log the exception for debugging purposes. + bittensor.logging.trace(f"Forward exception: {traceback.format_exc()}") + + # Set the status message of the synapse to the string representation of the exception. + synapse.axon.status_message = f"{str(e)}" + + # Calculate the processing time by subtracting the start time from the current time. + synapse.axon.process_time = str(time.time() - start_time) + + # Create a JSON response with a status code of 500 (internal server error), + # synapse headers, and an empty content. + response = JSONResponse( + status_code=500, headers=synapse.to_headers(), content={} + ) + + # Logs the end of request processing and returns the response + finally: + # Log the details of the processed synapse, including total size, name, hotkey, IP, port, + # status code, and status message, using the debug level of the logger. + bittensor.logging.debug( + f"axon | --> | {response.headers.get('content-length', -1)} B | {synapse.name} | {synapse.dendrite.hotkey} | {synapse.dendrite.ip}:{synapse.dendrite.port} | {synapse.axon.status_code} | {synapse.axon.status_message}" + ) + + # Return the response to the requester. + return response + + async def preprocess(self, request: Request) -> bittensor.Synapse: + """ + Perform preprocess operations for the request and generate the synapse state object. + + Args: + synapse (Synapse): The synapse instance representing the request. + """ + # Extracts the request name from the URL path. + request_name = request.url.path.split("/")[1] + + # Creates a synapse instance from the headers using the appropriate forward class type + # based on the request name obtained from the URL path. + synapse = self.axon.forward_class_types[request_name].from_headers( + request.headers + ) + synapse.name = request_name + + # Fills the local axon information into the synapse. + synapse.axon.__dict__.update( + { + "version": str(bittensor.__version_as_int__), + "uuid": str(self.axon.uuid), + "nonce": f"{time.monotonic_ns()}", + "status_message": "Success", + "status_code": "100", + } + ) + + # Fills the dendrite information into the synapse. + synapse.dendrite.__dict__.update( + {"port": str(request.client.port), "ip": str(request.client.host)} + ) + + # Signs the synapse from the axon side using the wallet hotkey. + message = f"{synapse.axon.nonce}.{synapse.dendrite.hotkey}.{synapse.axon.hotkey}.{synapse.axon.uuid}" + synapse.axon.signature = f"0x{self.axon.wallet.hotkey.sign(message).hex()}" + + # Return the setup synapse. + return synapse + + async def verify(self, synapse: bittensor.Synapse): + """ + Verify the request. + + Args: + synapse ( bittensor.Synapse ): The synapse instance representing the request. + + Raises: + Exception: If verification fails. + """ + # Start of the verification process. Verification is the process where we ensure that + # the incoming request is from a trusted source or fulfills certain requirements. + # We get a specific verification function from 'verify_fns' dictionary that corresponds + # to our request's name. Each request name (synapse name) has its unique verification function. + verify_fn = self.axon.verify_fns[synapse.name] + + # If a verification function exists for the request's name + if verify_fn: + try: + # We attempt to run the verification function using the synapse instance + # created from the request. If this function runs without throwing an exception, + # it means that the verification was successful. + await verify_fn(synapse) if inspect.iscoroutinefunction( + verify_fn + ) else verify_fn(synapse) + except Exception as e: + # If there was an exception during the verification process, we log that + # there was a verification exception. + bittensor.logging.trace(f"Verify exception {str(e)}") + + # We set the status code of the synapse to "401" which denotes an unauthorized access. + synapse.axon.status_code = "401" + + # We raise an exception to stop the process and return the error to the requester. + # The error message includes the original exception message. + raise Exception(f"Not Verified with error: {str(e)}") + + async def blacklist(self, synapse: bittensor.Synapse): + """ + Check if the request is blacklisted. + + Args: + synapse (Synapse): The synapse instance representing the request. + + Raises: + Exception: If the request is blacklisted. + """ + # A blacklist is a list of keys or identifiers + # that are prohibited from accessing certain resources. + # We retrieve the blacklist checking function from the 'blacklist_fns' dictionary + # that corresponds to the request's name (synapse name). + blacklist_fn = self.axon.blacklist_fns[synapse.name] + + # If a blacklist checking function exists for the request's name + if blacklist_fn: + # We execute the blacklist checking function using the synapse instance as input. + # If the function returns True, it means that the key or identifier is blacklisted. + blacklisted, reason = ( + await blacklist_fn(synapse) + if inspect.iscoroutinefunction(blacklist_fn) + else blacklist_fn(synapse) + ) + if blacklisted: + # We log that the key or identifier is blacklisted. + bittensor.logging.trace(f"Blacklisted: {blacklisted}, {reason}") + + # We set the status code of the synapse to "403" which indicates a forbidden access. + synapse.axon.status_code = "403" + + # We raise an exception to halt the process and return the error message to the requester. + raise Exception(f"Forbidden. Key is blacklisted: {reason}.") + + async def priority(self, synapse: bittensor.Synapse): + """ + Execute the priority function for the request. + + A priority function is a function that determines the priority or urgency of processing the request compared to other requests. + Args: + synapse (bittensor.Synapse): The synapse instance representing the request. + + Raises: + Exception: If the priority function times out. + """ + # Retrieve the priority function from the 'priority_fns' dictionary that corresponds + # to the request's name (synapse name). + priority_fn = self.axon.priority_fns[synapse.name] + + async def submit_task( + executor: bittensor.threadpool, priority: float + ) -> Tuple[float, Any]: + """ + Submits the given priority function to the specified executor for asynchronous execution. + The function will run in the provided executor and return the priority value along with the result. + + Args: + executor: The executor in which the priority function will be run. + priority: The priority function to be executed. + + Returns: + tuple: A tuple containing the priority value and the result of the priority function execution. + """ + loop = asyncio.get_event_loop() + future = loop.run_in_executor(executor, lambda: priority) + result = await future + return priority, result + + # If a priority function exists for the request's name + if priority_fn: + try: + # Execute the priority function and get the priority value. + priority = ( + await priority_fn(synapse) + if inspect.iscoroutinefunction(priority_fn) + else priority_fn(synapse) + ) + + # Submit the task to the thread pool for execution with the given priority. + # The submit_task function will handle the execution and return the result. + _, result = await submit_task(self.axon.thread_pool, priority) + + except TimeoutError as e: + # If the execution of the priority function exceeds the timeout, + # it raises an exception to handle the timeout error. + bittensor.logging.trace(f"TimeoutError: {str(e)}") + + # Set the status code of the synapse to "408" which indicates a timeout error. + synapse.axon.status_code = "408" + + # Raise an exception to stop the process and return an appropriate error message to the requester. + raise Exception(f"Response timeout after: {synapse.timeout}s") + + async def run( + self, + synapse: bittensor.Synapse, + call_next: RequestResponseEndpoint, + request: Request, + ) -> Response: + """ + Execute the requested function. + + Args: + synapse: ( bittensor.Synapse ): call state. + call_next: ( starlet RequestResponseEndpoint ): The function to call after processing the request. + request: ( starlet Request ): The incoming request. + + Returns: + response (starlet Response): The processed request. + """ + try: + # The requested function is executed by calling the 'call_next' function, + # passing the original request as an argument. This function processes the request + # and returns the response. + response = await call_next(request) + + except Exception as e: + # If an exception occurs during the execution of the requested function, + # it is caught and handled here. + + # Log the exception for debugging purposes. + bittensor.logging.trace(f"Run exception: {str(e)}") + + # Set the status code of the synapse to "500" which indicates an internal server error. + synapse.axon.status_code = "500" + + # Raise an exception to stop the process and return an appropriate error message to the requester. + raise Exception(f"Internal server error with error: {str(e)}") + + # Return the starlet response + return response + + async def postprocess( + self, synapse: bittensor.Synapse, response: Response, start_time: float + ) -> Response: + """ + Perform post-processing operations on the request. + + Args: + synapse (bittensor.Synapse): The synapse instance representing the request. + response (starlet Response): The response from the requested function. + start_time (float): The start time of request processing. + + Returns: + response (starlet Response): The processed request with updated headers. + """ + # Set the status code of the synapse to "200" which indicates a successful response. + synapse.axon.status_code = "200" + + # Set the status message of the synapse to "Success". + synapse.axon.status_message = "Success" + + # Calculate the processing time by subtracting the start time from the current time. + synapse.axon.process_time = str(time.time() - start_time) + + # Update the response headers with the headers from the synapse. + response.headers.update(synapse.to_headers()) + + return response diff --git a/bittensor/_subtensor/chain_data.py b/bittensor/chain_data.py similarity index 63% rename from bittensor/_subtensor/chain_data.py rename to bittensor/chain_data.py index 485e47d58f..33d465d915 100644 --- a/bittensor/_subtensor/chain_data.py +++ b/bittensor/chain_data.py @@ -15,16 +15,19 @@ # 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 dataclasses import dataclass -from typing import List, Tuple, Dict, Optional, Any, TypedDict -import bittensor -from bittensor import Balance, axon_info import torch +import bittensor + +from enum import Enum +from dataclasses import dataclass from scalecodec.types import GenericCall +from typing import List, Tuple, Dict, Optional, Any, TypedDict, Union from scalecodec.base import RuntimeConfiguration, ScaleBytes from scalecodec.type_registry import load_type_registry_preset from scalecodec.utils.ss58 import ss58_encode -from enum import Enum + +from .utils import networking as net, U16_MAX, U16_NORMALIZED_FLOAT +from .utils.balance import Balance custom_rpc_type_registry = { "types": { @@ -36,15 +39,10 @@ ["kappa", "Compact"], ["difficulty", "Compact"], ["immunity_period", "Compact"], - ["validator_batch_size", "Compact"], - ["validator_sequence_length", "Compact"], - ["validator_epochs_per_reset", "Compact"], - ["validator_epoch_length", "Compact"], ["max_allowed_validators", "Compact"], ["min_allowed_weights", "Compact"], ["max_weights_limit", "Compact"], ["scaling_law_power", "Compact"], - ["synergy_scaling_law_power", "Compact"], ["subnetwork_n", "Compact"], ["max_allowed_uids", "Compact"], ["blocks_since_last_step", "Compact"], @@ -53,6 +51,7 @@ ["network_connect", "Vec<[u16; 2]>"], ["emission_values", "Compact"], ["burn", "Compact"], + ["owner", "AccountId"], ], }, "DelegateInfo": { @@ -139,16 +138,128 @@ ["ip_type", "u8"], ], }, + "IPInfo": { + "type": "struct", + "type_mapping": [ + ["ip", "Compact"], + ["ip_type_and_protocol", "Compact"], + ], + }, + "StakeInfo": { + "type": "struct", + "type_mapping": [ + ["hotkey", "AccountId"], + ["coldkey", "AccountId"], + ["stake", "Compact"], + ], + }, + "SubnetHyperparameters": { + "type": "struct", + "type_mapping": [ + ["rho", "Compact"], + ["kappa", "Compact"], + ["immunity_period", "Compact"], + ["min_allowed_weights", "Compact"], + ["max_weights_limit", "Compact"], + ["tempo", "Compact"], + ["min_difficulty", "Compact"], + ["max_difficulty", "Compact"], + ["weights_version", "Compact"], + ["weights_rate_limit", "Compact"], + ["adjustment_interval", "Compact"], + ["activity_cutoff", "Compact"], + ["registration_allowed", "bool"], + ["target_regs_per_interval", "Compact"], + ["min_burn", "Compact"], + ["max_burn", "Compact"], + ["bonds_moving_avg", "Compact"], + ["max_regs_per_block", "Compact"], + ], + }, } } +@dataclass +class AxonInfo: + version: int + ip: str + port: int + ip_type: int + hotkey: str + coldkey: str + protocol: int = 4 + placeholder1: int = 0 + placeholder2: int = 0 + + @property + def is_serving(self) -> bool: + """True if the endpoint is serving.""" + if self.ip == "0.0.0.0": + return False + else: + return True + + def ip_str(self) -> str: + """Return the whole ip as string""" + return net.ip__str__(self.ip_type, self.ip, self.port) + + def __eq__(self, other: "AxonInfo"): + if other == None: + return False + if ( + self.version == other.version + and self.ip == other.ip + and self.port == other.port + and self.ip_type == other.ip_type + and self.coldkey == other.coldkey + and self.hotkey == other.hotkey + ): + return True + else: + return False + + def __str__(self): + return "AxonInfo( {}, {}, {}, {} )".format( + str(self.ip_str()), str(self.hotkey), str(self.coldkey), self.version + ) + + def __repr__(self): + return self.__str__() + + @classmethod + def from_neuron_info(cls, neuron_info: dict) -> "AxonInfo": + """Converts a dictionary to an axon_info object.""" + return cls( + version=neuron_info["axon_info"]["version"], + ip=net.int_to_ip(int(neuron_info["axon_info"]["ip"])), + port=neuron_info["axon_info"]["port"], + ip_type=neuron_info["axon_info"]["ip_type"], + hotkey=neuron_info["hotkey"], + 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__) + + @classmethod + def from_parameter_dict( + cls, parameter_dict: "torch.nn.ParameterDict" + ) -> "axon_info": + r"""Returns an axon_info object from a torch parameter_dict.""" + return cls(**dict(parameter_dict)) + + class ChainDataType(Enum): NeuronInfo = 1 SubnetInfo = 2 DelegateInfo = 3 NeuronInfoLite = 4 DelegatedInfo = 5 + StakeInfo = 6 + IPInfo = 7 + SubnetHyperparameters = 8 # Constants @@ -158,17 +269,11 @@ class ChainDataType(Enum): def from_scale_encoding( - vec_u8: List[int], + input: Union[List[int], bytes, ScaleBytes], type_name: ChainDataType, is_vec: bool = False, is_option: bool = False, ) -> Optional[Dict]: - as_bytes = bytes(vec_u8) - as_scale_bytes = ScaleBytes(as_bytes) - rpc_runtime_config = RuntimeConfiguration() - rpc_runtime_config.update_type_registry(load_type_registry_preset("legacy")) - rpc_runtime_config.update_type_registry(custom_rpc_type_registry) - type_string = type_name.name if type_name == ChainDataType.DelegatedInfo: # DelegatedInfo is a tuple of (DelegateInfo, Compact) @@ -178,6 +283,29 @@ def from_scale_encoding( if is_vec: type_string = f"Vec<{type_string}>" + return from_scale_encoding_using_type_string(input, type_string) + + +def from_scale_encoding_using_type_string( + input: Union[List[int], bytes, ScaleBytes], type_string: str +) -> Optional[Dict]: + if isinstance(input, ScaleBytes): + as_scale_bytes = input + else: + if isinstance(input, list) and all([isinstance(i, int) for i in input]): + vec_u8 = input + as_bytes = bytes(vec_u8) + elif isinstance(input, bytes): + as_bytes = input + else: + raise TypeError("input must be a List[int], bytes, or ScaleBytes") + + as_scale_bytes = ScaleBytes(as_bytes) + + rpc_runtime_config = RuntimeConfiguration() + rpc_runtime_config.update_type_registry(load_type_registry_preset("legacy")) + rpc_runtime_config.update_type_registry(custom_rpc_type_registry) + obj = rpc_runtime_config.create_scale_object(type_string, data=as_scale_bytes) return obj.decode() @@ -210,7 +338,7 @@ class NeuronInfo: weights: List[List[int]] bonds: List[List[int]] prometheus_info: "PrometheusInfo" - axon_info: "axon_info" + axon_info: "AxonInfo" pruning_score: int is_null: bool = False @@ -224,7 +352,7 @@ def fix_decoded_values(cls, neuron_info_decoded: Any) -> "NeuronInfo": neuron_info_decoded["coldkey"], bittensor.__ss58_format__ ) stake_dict = { - ss58_encode(coldkey, bittensor.__ss58_format__): bittensor.Balance.from_rao( + ss58_encode(coldkey, bittensor.__ss58_format__): Balance.from_rao( int(stake) ) for coldkey, stake in neuron_info_decoded["stake"] @@ -239,29 +367,27 @@ def fix_decoded_values(cls, neuron_info_decoded: Any) -> "NeuronInfo": neuron_info_decoded["bonds"] = [ [int(bond[0]), int(bond[1])] for bond in neuron_info_decoded["bonds"] ] - neuron_info_decoded["rank"] = bittensor.utils.U16_NORMALIZED_FLOAT( - neuron_info_decoded["rank"] - ) + neuron_info_decoded["rank"] = U16_NORMALIZED_FLOAT(neuron_info_decoded["rank"]) neuron_info_decoded["emission"] = neuron_info_decoded["emission"] / RAOPERTAO - neuron_info_decoded["incentive"] = bittensor.utils.U16_NORMALIZED_FLOAT( + neuron_info_decoded["incentive"] = U16_NORMALIZED_FLOAT( neuron_info_decoded["incentive"] ) - neuron_info_decoded["consensus"] = bittensor.utils.U16_NORMALIZED_FLOAT( + neuron_info_decoded["consensus"] = U16_NORMALIZED_FLOAT( neuron_info_decoded["consensus"] ) - neuron_info_decoded["trust"] = bittensor.utils.U16_NORMALIZED_FLOAT( + neuron_info_decoded["trust"] = U16_NORMALIZED_FLOAT( neuron_info_decoded["trust"] ) - neuron_info_decoded["validator_trust"] = bittensor.utils.U16_NORMALIZED_FLOAT( + neuron_info_decoded["validator_trust"] = U16_NORMALIZED_FLOAT( neuron_info_decoded["validator_trust"] ) - neuron_info_decoded["dividends"] = bittensor.utils.U16_NORMALIZED_FLOAT( + neuron_info_decoded["dividends"] = U16_NORMALIZED_FLOAT( neuron_info_decoded["dividends"] ) neuron_info_decoded["prometheus_info"] = PrometheusInfo.fix_decoded_values( neuron_info_decoded["prometheus_info"] ) - neuron_info_decoded["axon_info"] = bittensor.axon_info.from_neuron_info( + neuron_info_decoded["axon_info"] = AxonInfo.from_neuron_info( neuron_info_decoded ) @@ -401,7 +527,7 @@ def fix_decoded_values(cls, neuron_info_decoded: Any) -> "NeuronInfoLite": neuron_info_decoded["coldkey"], bittensor.__ss58_format__ ) stake_dict = { - ss58_encode(coldkey, bittensor.__ss58_format__): bittensor.Balance.from_rao( + ss58_encode(coldkey, bittensor.__ss58_format__): Balance.from_rao( int(stake) ) for coldkey, stake in neuron_info_decoded["stake"] @@ -412,29 +538,27 @@ def fix_decoded_values(cls, neuron_info_decoded: Any) -> "NeuronInfoLite": # Don't need weights and bonds in lite version # neuron_info_decoded['weights'] = [[int(weight[0]), int(weight[1])] for weight in neuron_info_decoded['weights']] # neuron_info_decoded['bonds'] = [[int(bond[0]), int(bond[1])] for bond in neuron_info_decoded['bonds']] - neuron_info_decoded["rank"] = bittensor.utils.U16_NORMALIZED_FLOAT( - neuron_info_decoded["rank"] - ) + neuron_info_decoded["rank"] = U16_NORMALIZED_FLOAT(neuron_info_decoded["rank"]) neuron_info_decoded["emission"] = neuron_info_decoded["emission"] / RAOPERTAO - neuron_info_decoded["incentive"] = bittensor.utils.U16_NORMALIZED_FLOAT( + neuron_info_decoded["incentive"] = U16_NORMALIZED_FLOAT( neuron_info_decoded["incentive"] ) - neuron_info_decoded["consensus"] = bittensor.utils.U16_NORMALIZED_FLOAT( + neuron_info_decoded["consensus"] = U16_NORMALIZED_FLOAT( neuron_info_decoded["consensus"] ) - neuron_info_decoded["trust"] = bittensor.utils.U16_NORMALIZED_FLOAT( + neuron_info_decoded["trust"] = U16_NORMALIZED_FLOAT( neuron_info_decoded["trust"] ) - neuron_info_decoded["validator_trust"] = bittensor.utils.U16_NORMALIZED_FLOAT( + neuron_info_decoded["validator_trust"] = U16_NORMALIZED_FLOAT( neuron_info_decoded["validator_trust"] ) - neuron_info_decoded["dividends"] = bittensor.utils.U16_NORMALIZED_FLOAT( + neuron_info_decoded["dividends"] = U16_NORMALIZED_FLOAT( neuron_info_decoded["dividends"] ) neuron_info_decoded["prometheus_info"] = PrometheusInfo.fix_decoded_values( neuron_info_decoded["prometheus_info"] ) - neuron_info_decoded["axon_info"] = axon_info.from_neuron_info( + neuron_info_decoded["axon_info"] = AxonInfo.from_neuron_info( neuron_info_decoded ) return cls(**neuron_info_decoded) @@ -534,7 +658,7 @@ class PrometheusInfo: @classmethod def fix_decoded_values(cls, prometheus_info_decoded: Dict) -> "PrometheusInfo": r"""Returns a PrometheusInfo object from a prometheus_info_decoded dictionary.""" - prometheus_info_decoded["ip"] = bittensor.utils.networking.int_to_ip( + prometheus_info_decoded["ip"] = net.int_to_ip( int(prometheus_info_decoded["ip"]) ) @@ -557,8 +681,8 @@ class DelegateInfo: int ] # List of subnets that the delegate is allowed to validate on registrations: List[int] # List of subnets that the delegate is registered on - return_per_1000: bittensor.Balance # Return per 1000 tao of the delegate over a day - total_daily_return: bittensor.Balance # Total daily return of the delegate + return_per_1000: Balance # Return per 1000 tao of the delegate over a day + total_daily_return: Balance # Total daily return of the delegate @classmethod def fix_decoded_values(cls, decoded: Any) -> "DelegateInfo": @@ -569,7 +693,7 @@ def fix_decoded_values(cls, decoded: Any) -> "DelegateInfo": decoded["delegate_ss58"], bittensor.__ss58_format__ ), owner_ss58=ss58_encode(decoded["owner_ss58"], bittensor.__ss58_format__), - take=bittensor.utils.U16_NORMALIZED_FLOAT(decoded["take"]), + take=U16_NORMALIZED_FLOAT(decoded["take"]), nominators=[ ( ss58_encode(nom[0], bittensor.__ss58_format__), @@ -582,10 +706,8 @@ def fix_decoded_values(cls, decoded: Any) -> "DelegateInfo": ), validator_permits=decoded["validator_permits"], registrations=decoded["registrations"], - return_per_1000=bittensor.Balance.from_rao(decoded["return_per_1000"]), - total_daily_return=bittensor.Balance.from_rao( - decoded["total_daily_return"] - ), + return_per_1000=Balance.from_rao(decoded["return_per_1000"]), + total_daily_return=Balance.from_rao(decoded["total_daily_return"]), ) @classmethod @@ -635,6 +757,76 @@ def delegated_list_from_vec_u8( return decoded +@dataclass +class StakeInfo: + r""" + Dataclass for stake info. + """ + hotkey_ss58: str # Hotkey address + coldkey_ss58: str # Coldkey address + stake: Balance # Stake for the hotkey-coldkey pair + + @classmethod + def fix_decoded_values(cls, decoded: Any) -> "StakeInfo": + r"""Fixes the decoded values.""" + + return cls( + hotkey_ss58=ss58_encode(decoded["hotkey"], bittensor.__ss58_format__), + coldkey_ss58=ss58_encode(decoded["coldkey"], bittensor.__ss58_format__), + stake=Balance.from_rao(decoded["stake"]), + ) + + @classmethod + def from_vec_u8(cls, vec_u8: List[int]) -> Optional["StakeInfo"]: + r"""Returns a StakeInfo object from a vec_u8.""" + if len(vec_u8) == 0: + return None + + decoded = from_scale_encoding(vec_u8, ChainDataType.StakeInfo) + + if decoded is None: + return None + + decoded = StakeInfo.fix_decoded_values(decoded) + + return decoded + + @classmethod + def list_of_tuple_from_vec_u8( + cls, vec_u8: List[int] + ) -> Dict[str, List["StakeInfo"]]: + r"""Returns a list of StakeInfo objects from a vec_u8.""" + decoded: Optional[ + List[Tuple(str, List[object])] + ] = from_scale_encoding_using_type_string( + input=vec_u8, type_string="Vec<(AccountId, Vec)>" + ) + + if decoded is None: + return {} + + stake_map = { + ss58_encode(address=account_id, ss58_format=bittensor.__ss58_format__): [ + StakeInfo.fix_decoded_values(d) for d in stake_info + ] + for account_id, stake_info in decoded + } + + return stake_map + + @classmethod + def list_from_vec_u8(cls, vec_u8: List[int]) -> List["StakeInfo"]: + r"""Returns a list of StakeInfo objects from a vec_u8.""" + decoded = from_scale_encoding(vec_u8, ChainDataType.StakeInfo, is_vec=True) + + if decoded is None: + return [] + + decoded = [StakeInfo.fix_decoded_values(d) for d in decoded] + + return decoded + + @dataclass class SubnetInfo: r""" @@ -645,15 +837,10 @@ class SubnetInfo: kappa: int difficulty: int immunity_period: int - validator_batch_size: int - validator_sequence_length: int - validator_epochs_per_reset: int - validator_epoch_length: int max_allowed_validators: int min_allowed_weights: int max_weight_limit: float scaling_law_power: float - synergy_scaling_law_power: float subnetwork_n: int max_n: int blocks_since_epoch: int @@ -663,6 +850,7 @@ class SubnetInfo: connection_requirements: Dict[str, float] emission_value: float burn: Balance + owner_ss58: str @classmethod def from_vec_u8(cls, vec_u8: List[int]) -> Optional["SubnetInfo"]: @@ -700,26 +888,22 @@ def fix_decoded_values(cls, decoded: Dict) -> "SubnetInfo": kappa=decoded["kappa"], difficulty=decoded["difficulty"], immunity_period=decoded["immunity_period"], - validator_batch_size=decoded["validator_batch_size"], - validator_sequence_length=decoded["validator_sequence_length"], - validator_epochs_per_reset=decoded["validator_epochs_per_reset"], - validator_epoch_length=decoded["validator_epoch_length"], max_allowed_validators=decoded["max_allowed_validators"], min_allowed_weights=decoded["min_allowed_weights"], max_weight_limit=decoded["max_weights_limit"], scaling_law_power=decoded["scaling_law_power"], - synergy_scaling_law_power=decoded["synergy_scaling_law_power"], subnetwork_n=decoded["subnetwork_n"], max_n=decoded["max_allowed_uids"], blocks_since_epoch=decoded["blocks_since_last_step"], tempo=decoded["tempo"], modality=decoded["network_modality"], connection_requirements={ - str(int(netuid)): bittensor.utils.U16_NORMALIZED_FLOAT(int(req)) + str(int(netuid)): U16_NORMALIZED_FLOAT(int(req)) for netuid, req in decoded["network_connect"] }, emission_value=decoded["emission_values"], burn=Balance.from_rao(decoded["burn"]), + owner_ss58=ss58_encode(decoded["owner"], bittensor.__ss58_format__), ) def to_parameter_dict(self) -> "torch.nn.ParameterDict": @@ -734,6 +918,155 @@ def from_parameter_dict( return cls(**dict(parameter_dict)) +@dataclass +class SubnetHyperparameters: + r""" + Dataclass for subnet hyperparameters. + """ + rho: int + kappa: int + immunity_period: int + min_allowed_weights: int + max_weight_limit: float + tempo: int + min_difficulty: int + max_difficulty: int + weights_version: int + weights_rate_limit: int + adjustment_interval: int + activity_cutoff: int + registration_allowed: bool + target_regs_per_interval: int + min_burn: int + max_burn: int + bonds_moving_avg: int + max_regs_per_block: int + + @classmethod + def from_vec_u8(cls, vec_u8: List[int]) -> Optional["SubnetHyperparameters"]: + r"""Returns a SubnetHyperparameters object from a vec_u8.""" + if len(vec_u8) == 0: + return None + + decoded = from_scale_encoding(vec_u8, ChainDataType.SubnetHyperparameters) + + if decoded is None: + return None + + return SubnetHyperparameters.fix_decoded_values(decoded) + + @classmethod + def list_from_vec_u8(cls, vec_u8: List[int]) -> List["SubnetHyperparameters"]: + r"""Returns a list of SubnetHyperparameters objects from a vec_u8.""" + decoded = from_scale_encoding( + vec_u8, ChainDataType.SubnetHyperparameters, is_vec=True, is_option=True + ) + + if decoded is None: + return [] + + decoded = [SubnetHyperparameters.fix_decoded_values(d) for d in decoded] + + return decoded + + @classmethod + def fix_decoded_values(cls, decoded: Dict) -> "SubnetHyperparameters": + r"""Returns a SubnetInfo object from a decoded SubnetInfo dictionary.""" + return SubnetHyperparameters( + rho=decoded["rho"], + kappa=decoded["kappa"], + immunity_period=decoded["immunity_period"], + min_allowed_weights=decoded["min_allowed_weights"], + max_weight_limit=decoded["max_weights_limit"], + tempo=decoded["tempo"], + min_difficulty=decoded["min_difficulty"], + max_difficulty=decoded["max_difficulty"], + weights_version=decoded["weights_version"], + weights_rate_limit=decoded["weights_rate_limit"], + adjustment_interval=decoded["adjustment_interval"], + activity_cutoff=decoded["activity_cutoff"], + registration_allowed=decoded["registration_allowed"], + target_regs_per_interval=decoded["target_regs_per_interval"], + min_burn=decoded["min_burn"], + max_burn=decoded["max_burn"], + bonds_moving_avg=decoded["bonds_moving_avg"], + max_regs_per_block=decoded["max_regs_per_block"], + ) + + def to_parameter_dict(self) -> "torch.nn.ParameterDict": + r"""Returns a torch tensor of the subnet hyperparameters.""" + return torch.nn.ParameterDict(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)) + + +@dataclass +class IPInfo: + r""" + Dataclass for associated IP Info. + """ + ip: str + ip_type: int + protocol: int + + def encode(self) -> Dict[str, Any]: + r"""Returns a dictionary of the IPInfo object that can be encoded.""" + return { + "ip": net.ip_to_int( + self.ip + ), # IP type and protocol are encoded together as a u8 + "ip_type_and_protocol": ((self.ip_type << 4) + self.protocol) & 0xFF, + } + + @classmethod + def from_vec_u8(cls, vec_u8: List[int]) -> Optional["IPInfo"]: + r"""Returns a IPInfo object from a vec_u8.""" + if len(vec_u8) == 0: + return None + + decoded = from_scale_encoding(vec_u8, ChainDataType.IPInfo) + + if decoded is None: + return None + + return IPInfo.fix_decoded_values(decoded) + + @classmethod + def list_from_vec_u8(cls, vec_u8: List[int]) -> List["IPInfo"]: + r"""Returns a list of IPInfo objects from a vec_u8.""" + decoded = from_scale_encoding(vec_u8, ChainDataType.IPInfo, is_vec=True) + + if decoded is None: + return [] + + decoded = [IPInfo.fix_decoded_values(d) for d in decoded] + + return decoded + + @classmethod + def fix_decoded_values(cls, decoded: Dict) -> "IPInfo": + r"""Returns a SubnetInfo object from a decoded IPInfo dictionary.""" + return IPInfo( + ip=bittensor.utils.networking.int_to_ip(decoded["ip"]), + ip_type=decoded["ip_type_and_protocol"] >> 4, + 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__) + + @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)) + + # Senate / Proposal data diff --git a/bittensor/cli.py b/bittensor/cli.py new file mode 100644 index 0000000000..1d1ef56561 --- /dev/null +++ b/bittensor/cli.py @@ -0,0 +1,278 @@ +# The MIT License (MIT) +# Copyright © 2021 Yuma Rao + +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the “Software”), to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of +# the Software. + +# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# 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 sys +import argparse +import bittensor +from typing import List, Optional +from .commands import * + +# Create a console instance for CLI display. +console = bittensor.__console__ + +ALIAS_TO_COMMAND = { + "subnets": "subnets", + "root": "root", + "wallet": "wallet", + "stake": "stake", + "sudo": "sudo", + "legacy": "legacy", + "s": "subnets", + "r": "root", + "w": "wallet", + "st": "stake", + "su": "sudo", + "l": "legacy", + "subnet": "subnets", + "roots": "root", + "wallets": "wallet", + "stakes": "stake", + "sudos": "sudo", +} +COMMANDS = { + "subnets": { + "name": "subnets", + "aliases": ["s", "subnet"], + "help": "Commands for managing and viewing subnetworks.", + "commands": { + "list": SubnetListCommand, + "metagraph": MetagraphCommand, + "lock_cost": SubnetLockCostCommand, + "create": RegisterSubnetworkCommand, + "register": RegisterCommand, + "recycle_register": RecycleRegisterCommand, + "hyperparameters": SubnetHyperparamsCommand, + }, + }, + "root": { + "name": "root", + "aliases": ["r", "roots"], + "help": "Commands for managing and viewing the root network.", + "commands": { + "list": RootList, + "weights": RootSetWeightsCommand, + "senate_vote": VoteCommand, + "register": RootRegisterCommand, + "proposals": ProposalsCommand, + "delegate": DelegateStakeCommand, + "undelegate": DelegateUnstakeCommand, + "my_delegates": MyDelegatesCommand, + "list_delegates": ListDelegatesCommand, + "nominate": NominateCommand, + }, + }, + "wallet": { + "name": "wallet", + "aliases": ["w", "wallets"], + "help": "Commands for managing and viewing wallets.", + "commands": { + "list": ListCommand, + "overview": OverviewCommand, + "transfer": TransferCommand, + "inspect": InspectCommand, + # "balance": None, + "create": WalletCreateCommand, + "new_hotkey": NewHotkeyCommand, + "new_coldkey": NewColdkeyCommand, + "regen_coldkey": RegenColdkeyCommand, + "regen_coldkeypub": RegenColdkeypubCommand, + "regen_hotkey": RegenHotkeyCommand, + "faucet": RunFaucetCommand, + "update": UpdateWalletCommand, + }, + }, + "stake": { + "name": "stake", + "aliases": ["st", "stakes"], + "help": "Commands for staking and removing stake from hotkey accounts.", + "commands": { + "show": StakeShow, + "add": StakeCommand, + "remove": UnStakeCommand, + }, + }, + "sudo": { + "name": "sudo", + "aliases": ["su", "sudos"], + "help": "Commands for subnet management", + "commands": { + # "dissolve": None, + "set": SubnetSudoCommand, + "get": SubnetGetHyperparamsCommand, + }, + }, + "legacy": { + "name": "legacy", + "aliases": ["l"], + "help": "Miscellaneous commands.", + "commands": { + "update": UpdateCommand, + "faucet": RunFaucetCommand, + }, + }, +} + + +class cli: + """ + Implementation of the Command Line Interface (CLI) class for the Bittensor protocol. + This class handles operations like key management (hotkey and coldkey) and token transfer. + """ + + def __init__( + self, + config: Optional["bittensor.config"] = None, + args: Optional[List[str]] = None, + ): + """ + Initializes a bittensor.CLI object. + + Args: + config (bittensor.config, optional): The configuration settings for the CLI. + args (List[str], optional): List of command line arguments. + """ + # Turns on console for cli. + bittensor.turn_console_on() + + # If no config is provided, create a new one from args. + if config == None: + config = cli.create_config(args) + + self.config = config + if self.config.command in ALIAS_TO_COMMAND: + self.config.command = ALIAS_TO_COMMAND[self.config.command] + else: + console.print( + f":cross_mark:[red]Unknown command: {self.config.command}[/red]" + ) + sys.exit() + + # Check if the config is valid. + cli.check_config(self.config) + + # If no_version_checking is not set or set as False in the config, version checking is done. + if not self.config.get("no_version_checking", d=True): + try: + bittensor.utils.version_checking() + except: + # If version checking fails, inform user with an exception. + raise RuntimeError( + "To avoid internet-based version checking, pass --no_version_checking while running the CLI." + ) + + @staticmethod + def __create_parser__() -> "argparse.ArgumentParser": + """ + Creates the argument parser for the Bittensor CLI. + + Returns: + argparse.ArgumentParser: An argument parser object for Bittensor CLI. + """ + # Define the basic argument parser. + parser = argparse.ArgumentParser( + description=f"bittensor cli v{bittensor.__version__}", + usage="btcli ", + add_help=True, + ) + # Add arguments for each sub-command. + cmd_parsers = parser.add_subparsers(dest="command") + # Add argument parsers for all available commands. + for command in COMMANDS.values(): + if isinstance(command, dict): + subcmd_parser = cmd_parsers.add_parser( + name=command["name"], + aliases=command["aliases"], + help=command["help"], + ) + subparser = subcmd_parser.add_subparsers( + help=command["help"], dest="subcommand", required=True + ) + + for subcommand in command["commands"].values(): + subcommand.add_args(subparser) + else: + command.add_args(cmd_parsers) + + return parser + + @staticmethod + def create_config(args: List[str]) -> "bittensor.config": + """ + From the argument parser, add config to bittensor.executor and local config + + Args: + args (List[str]): List of command line arguments. + + Returns: + bittensor.config: The configuration object for Bittensor CLI. + """ + parser = cli.__create_parser__() + + # If no arguments are passed, print help text and exit the program. + if len(args) == 0: + parser.print_help() + sys.exit() + + return bittensor.config(parser, args=args) + + @staticmethod + def check_config(config: "bittensor.config"): + """ + Checks if the essential configuration exists under different command + + Args: + config (bittensor.config): The configuration settings for the CLI. + """ + # Check if command exists, if so, run the corresponding check_config. + # If command doesn't exist, inform user and exit the program. + if config.command in COMMANDS: + command = config.command + command_data = COMMANDS[command] + + if isinstance(command_data, dict): + if config["subcommand"] != None: + command_data["commands"][config["subcommand"]].check_config(config) + else: + console.print( + f":cross_mark:[red]Missing subcommand for: {config.command}[/red]" + ) + sys.exit(1) + else: + command_data.check_config(config) + else: + console.print(f":cross_mark:[red]Unknown command: {config.command}[/red]") + sys.exit(1) + + def run(self): + """ + Executes the command from the configuration. + """ + # Check if command exists, if so, run the corresponding method. + # If command doesn't exist, inform user and exit the program. + command = self.config.command + if command in COMMANDS: + command_data = COMMANDS[command] + + if isinstance(command_data, dict): + command_data["commands"][self.config["subcommand"]].run(self) + else: + command_data.run(self) + else: + console.print( + f":cross_mark:[red]Unknown command: {self.config.command}[/red]" + ) + sys.exit() diff --git a/bittensor/commands/__init__.py b/bittensor/commands/__init__.py new file mode 100644 index 0000000000..f48d0bb3bf --- /dev/null +++ b/bittensor/commands/__init__.py @@ -0,0 +1,106 @@ +# The MIT License (MIT) +# Copyright © 2023 Opentensor Technologies Inc + +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the “Software”), to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of +# the Software. + +# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# 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. + +from munch import Munch, munchify + +defaults: Munch = munchify( + { + "netuid": 1, + "subtensor": {"network": "finney", "chain_endpoint": None, "_mock": False}, + "register": { + "num_processes": None, + "update_interval": 50000, + "output_in_place": True, + "verbose": False, + "cuda": {"dev_id": [0], "use_cuda": False, "TPB": 256}, + }, + "axon": { + "port": 8091, + "ip": "[::]", + "external_port": None, + "external_ip": None, + "max_workers": 10, + "maximum_concurrent_rpcs": 400, + }, + "priority": {"max_workers": 5, "maxsize": 10}, + "prometheus": {"port": 7091, "level": "INFO"}, + "wallet": { + "name": "default", + "hotkey": "default", + "path": "~/.bittensor/wallets/", + }, + "dataset": { + "batch_size": 10, + "block_size": 20, + "num_workers": 0, + "dataset_names": "default", + "data_dir": "~/.bittensor/data/", + "save_dataset": False, + "max_datasets": 3, + "num_batches": 100, + }, + "logging": { + "debug": False, + "trace": False, + "record_log": False, + "logging_dir": "~/.bittensor/miners", + }, + } +) + +from .stake import StakeCommand, StakeShow +from .unstake import UnStakeCommand +from .overview import OverviewCommand +from .register import RegisterCommand, RecycleRegisterCommand, RunFaucetCommand +from .delegates import ( + NominateCommand, + ListDelegatesCommand, + DelegateStakeCommand, + DelegateUnstakeCommand, + MyDelegatesCommand, +) +from .wallets import ( + NewColdkeyCommand, + NewHotkeyCommand, + RegenColdkeyCommand, + RegenColdkeypubCommand, + RegenHotkeyCommand, + UpdateWalletCommand, + WalletCreateCommand, +) +from .transfer import TransferCommand +from .inspect import InspectCommand +from .metagraph import MetagraphCommand +from .list import ListCommand +from .misc import UpdateCommand, ListSubnetsCommand +from .senate import ( + SenateCommand, + ProposalsCommand, + ShowVotesCommand, + SenateRegisterCommand, + SenateLeaveCommand, + VoteCommand, +) +from .network import ( + RegisterSubnetworkCommand, + SubnetLockCostCommand, + SubnetListCommand, + SubnetSudoCommand, + SubnetHyperparamsCommand, + SubnetGetHyperparamsCommand, +) +from .root import RootRegisterCommand, RootList, RootSetWeightsCommand diff --git a/bittensor/_cli/commands/delegates.py b/bittensor/commands/delegates.py similarity index 88% rename from bittensor/_cli/commands/delegates.py rename to bittensor/commands/delegates.py index d3ff1e1480..b622abf6fb 100644 --- a/bittensor/_cli/commands/delegates.py +++ b/bittensor/commands/delegates.py @@ -17,7 +17,6 @@ import sys import os -import json import argparse import bittensor from typing import List, Optional @@ -28,6 +27,7 @@ from tqdm import tqdm from substrateinterface.exceptions import SubstrateRequestException from .utils import get_delegates_details, DelegatesDetails +from . import defaults import os import bittensor @@ -187,7 +187,7 @@ def run(cli): """Delegates stake to a chain delegate.""" config = cli.config.copy() wallet = bittensor.wallet(config=config) - subtensor: bittensor.Subtensor = bittensor.subtensor(config=config) + subtensor: bittensor.subtensor = bittensor.subtensor(config=config) subtensor.delegate( wallet=wallet, delegate_ss58=config.get("delegate_ss58key"), @@ -201,12 +201,6 @@ def add_args(parser: argparse.ArgumentParser): delegate_stake_parser = parser.add_parser( "delegate", help="""Delegate Stake to an account.""" ) - delegate_stake_parser.add_argument( - "--no_version_checking", - action="store_true", - help="""Set false to stop cli version checking""", - default=False, - ) delegate_stake_parser.add_argument( "--delegate_ss58key", "--delegate_ss58", @@ -221,18 +215,11 @@ def add_args(parser: argparse.ArgumentParser): delegate_stake_parser.add_argument( "--amount", dest="amount", type=float, required=False ) - delegate_stake_parser.add_argument( - "--no_prompt", - dest="no_prompt", - action="store_true", - help="""Set true to avoid prompting the user.""", - default=False, - ) bittensor.wallet.add_args(delegate_stake_parser) bittensor.subtensor.add_args(delegate_stake_parser) @staticmethod - def check_config(config: "bittensor.Config"): + def check_config(config: "bittensor.config"): if not config.get("delegate_ss58key"): # Check for delegates. with bittensor.__console__.status(":satellite: Loading delegates..."): @@ -267,16 +254,14 @@ def check_config(config: "bittensor.Config"): ) if not config.is_set("wallet.name") and not config.no_prompt: - wallet_name = Prompt.ask( - "Enter wallet name", default=bittensor.defaults.wallet.name - ) + wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) config.wallet.name = str(wallet_name) # Get amount. if not config.get("amount") and not config.get("stake_all"): if not Confirm.ask( "Stake all Tao from account: [bold]'{}'[/bold]?".format( - config.wallet.get("name", bittensor.defaults.wallet.name) + config.wallet.get("name", defaults.wallet.name) ) ): amount = Prompt.ask("Enter Tao amount to stake") @@ -299,7 +284,7 @@ def run(cli): """Undelegates stake from a chain delegate.""" config = cli.config.copy() wallet = bittensor.wallet(config=config) - subtensor: bittensor.Subtensor = bittensor.subtensor(config=config) + subtensor: bittensor.subtensor = bittensor.subtensor(config=config) subtensor.undelegate( wallet=wallet, delegate_ss58=config.get("delegate_ss58key"), @@ -313,12 +298,6 @@ def add_args(parser: argparse.ArgumentParser): undelegate_stake_parser = parser.add_parser( "undelegate", help="""Undelegate Stake from an account.""" ) - undelegate_stake_parser.add_argument( - "--no_version_checking", - action="store_true", - help="""Set false to stop cli version checking""", - default=False, - ) undelegate_stake_parser.add_argument( "--delegate_ss58key", "--delegate_ss58", @@ -333,22 +312,13 @@ def add_args(parser: argparse.ArgumentParser): undelegate_stake_parser.add_argument( "--amount", dest="amount", type=float, required=False ) - undelegate_stake_parser.add_argument( - "--no_prompt", - dest="no_prompt", - action="store_true", - help="""Set true to avoid prompting the user.""", - default=False, - ) bittensor.wallet.add_args(undelegate_stake_parser) bittensor.subtensor.add_args(undelegate_stake_parser) @staticmethod - def check_config(config: "bittensor.Config"): + def check_config(config: "bittensor.config"): if not config.is_set("wallet.name") and not config.no_prompt: - wallet_name = Prompt.ask( - "Enter wallet name", default=bittensor.defaults.wallet.name - ) + wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) config.wallet.name = str(wallet_name) if not config.get("delegate_ss58key"): @@ -388,7 +358,7 @@ def check_config(config: "bittensor.Config"): if not config.get("amount") and not config.get("unstake_all"): if not Confirm.ask( "Unstake all Tao to account: [bold]'{}'[/bold]?".format( - config.wallet.get("name", bittensor.defaults.wallet.name) + config.wallet.get("name", defaults.wallet.name) ) ): amount = Prompt.ask("Enter Tao amount to unstake") @@ -435,17 +405,10 @@ def add_args(parser: argparse.ArgumentParser): list_delegates_parser = parser.add_parser( "list_delegates", help="""List all delegates on the network""" ) - list_delegates_parser.add_argument( - "--no_prompt", - dest="no_prompt", - action="store_true", - help="""Set true to avoid prompting the user.""", - default=False, - ) bittensor.subtensor.add_args(list_delegates_parser) @staticmethod - def check_config(config: "bittensor.Config"): + def check_config(config: "bittensor.config"): pass @@ -497,28 +460,17 @@ def add_args(parser: argparse.ArgumentParser): nominate_parser = parser.add_parser( "nominate", help="""Become a delegate on the network""" ) - nominate_parser.add_argument( - "--no_prompt", - dest="no_prompt", - action="store_true", - help="""Set true to avoid prompting the user.""", - default=False, - ) bittensor.wallet.add_args(nominate_parser) bittensor.subtensor.add_args(nominate_parser) @staticmethod - def check_config(config: "bittensor.Config"): + def check_config(config: "bittensor.config"): if not config.is_set("wallet.name") and not config.no_prompt: - wallet_name = Prompt.ask( - "Enter wallet name", default=bittensor.defaults.wallet.name - ) + wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) config.wallet.name = str(wallet_name) if not config.is_set("wallet.hotkey") and not config.no_prompt: - hotkey = Prompt.ask( - "Enter hotkey name", default=bittensor.defaults.wallet.hotkey - ) + hotkey = Prompt.ask("Enter hotkey name", default=defaults.wallet.hotkey) config.wallet.hotkey = str(hotkey) @@ -531,7 +483,7 @@ def run(cli): wallets = _get_coldkey_wallets_for_path(config.wallet.path) else: wallets = [bittensor.wallet(config=config)] - subtensor: bittensor.Subtensor = bittensor.subtensor(config=config) + subtensor: bittensor.subtensor = bittensor.subtensor(config=config) table = Table(show_footer=True, pad_edge=False, box=None, expand=True) table.add_column( @@ -657,36 +609,21 @@ def add_args(parser: argparse.ArgumentParser): "my_delegates", help="""Show all delegates where I am delegating a positive amount of stake""", ) - delegate_stake_parser.add_argument( - "--no_version_checking", - action="store_true", - help="""Set false to stop cli version checking""", - default=False, - ) delegate_stake_parser.add_argument( "--all", action="store_true", help="""Check all coldkey wallets.""", default=False, ) - delegate_stake_parser.add_argument( - "--no_prompt", - dest="no_prompt", - action="store_true", - help="""Set true to avoid prompting the user.""", - default=False, - ) bittensor.wallet.add_args(delegate_stake_parser) bittensor.subtensor.add_args(delegate_stake_parser) @staticmethod - def check_config(config: "bittensor.Config"): + def check_config(config: "bittensor.config"): if ( not config.get("all", d=None) and not config.is_set("wallet.name") and not config.no_prompt ): - wallet_name = Prompt.ask( - "Enter wallet name", default=bittensor.defaults.wallet.name - ) + wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) config.wallet.name = str(wallet_name) diff --git a/bittensor/_cli/commands/inspect.py b/bittensor/commands/inspect.py similarity index 89% rename from bittensor/_cli/commands/inspect.py rename to bittensor/commands/inspect.py index 6a6144e586..4b24a2da17 100644 --- a/bittensor/_cli/commands/inspect.py +++ b/bittensor/commands/inspect.py @@ -22,6 +22,7 @@ from rich.table import Table from rich.prompt import Prompt from .utils import check_netuid_set, get_delegates_details, DelegatesDetails +from . import defaults console = bittensor.__console__ @@ -85,7 +86,8 @@ def run(cli): neuron_state_dict = {} for netuid in tqdm(netuids): - neuron_state_dict[netuid] = subtensor.neurons_lite(netuid) + neurons = subtensor.neurons_lite(netuid) + neuron_state_dict[netuid] = neurons if neurons != None else [] table = Table(show_footer=True, pad_edge=False, box=None, expand=True) table.add_column( @@ -122,17 +124,7 @@ def run(cli): if not wallet.coldkeypub_file.exists_on_device(): continue cold_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address) - table.add_row( - wallet.name, - str(cold_balance), - "", - "", - "", - "", - "", - "", - "", - ) + table.add_row(wallet.name, str(cold_balance), "", "", "", "", "", "", "") for dele, staked in delegates: if dele.hotkey_ss58 in registered_delegate_info: delegate_name = registered_delegate_info[dele.hotkey_ss58].name @@ -185,15 +177,13 @@ def run(cli): bittensor.__console__.print(table) @staticmethod - def check_config(config: "bittensor.Config"): + def check_config(config: "bittensor.config"): if ( not config.get("all", d=None) and not config.is_set("wallet.name") and not config.no_prompt ): - wallet_name = Prompt.ask( - "Enter wallet name", default=bittensor.defaults.wallet.name - ) + wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) config.wallet.name = str(wallet_name) @staticmethod @@ -207,18 +197,6 @@ def add_args(parser: argparse.ArgumentParser): help="""Check all coldkey wallets.""", default=False, ) - inspect_parser.add_argument( - "--no_prompt", - dest="no_prompt", - action="store_true", - help="""Set true to avoid prompting the user.""", - default=False, - ) - inspect_parser.add_argument( - "--no_version_checking", - action="store_true", - help="""Set false to stop cli version checking""", - default=False, - ) + bittensor.wallet.add_args(inspect_parser) bittensor.subtensor.add_args(inspect_parser) diff --git a/bittensor/_cli/commands/list.py b/bittensor/commands/list.py similarity index 88% rename from bittensor/_cli/commands/list.py rename to bittensor/commands/list.py index 4aa39d370b..209ddf979d 100644 --- a/bittensor/_cli/commands/list.py +++ b/bittensor/commands/list.py @@ -80,24 +80,11 @@ def run(cli): print(root) @staticmethod - def check_config(config: "bittensor.Config"): + def check_config(config: "bittensor.config"): pass @staticmethod def add_args(parser: argparse.ArgumentParser): list_parser = parser.add_parser("list", help="""List wallets""") - list_parser.add_argument( - "--no_prompt", - dest="no_prompt", - action="store_true", - help="""Set true to avoid prompting the user.""", - default=False, - ) - list_parser.add_argument( - "--no_version_checking", - action="store_true", - help="""Set false to stop cli version checking""", - default=False, - ) bittensor.wallet.add_args(list_parser) bittensor.subtensor.add_args(list_parser) diff --git a/bittensor/_cli/commands/metagraph.py b/bittensor/commands/metagraph.py similarity index 92% rename from bittensor/_cli/commands/metagraph.py rename to bittensor/commands/metagraph.py index 9aef93a51b..61b35695e1 100644 --- a/bittensor/_cli/commands/metagraph.py +++ b/bittensor/commands/metagraph.py @@ -41,7 +41,7 @@ def run(cli): subnet_emission = bittensor.Balance.from_tao( subtensor.get_emission_value_by_subnet(cli.config.netuid) ) - total_issuance = bittensor.Balance.from_tao(subtensor.total_issuance()) + total_issuance = bittensor.Balance.from_rao(subtensor.total_issuance().rao) TABLE_DATA = [] total_stake = 0.0 @@ -187,12 +187,14 @@ def run(cli): console.print(table) @staticmethod - def check_config(config: "bittensor.Config"): + def check_config(config: "bittensor.config"): check_netuid_set(config, subtensor=bittensor.subtensor(config=config)) @staticmethod def add_args(parser: argparse.ArgumentParser): - metagraph_parser = parser.add_parser("metagraph", help="""Metagraph commands""") + metagraph_parser = parser.add_parser( + "metagraph", help="""View a subnet metagraph information.""" + ) metagraph_parser.add_argument( "--netuid", dest="netuid", @@ -200,17 +202,5 @@ def add_args(parser: argparse.ArgumentParser): help="""Set the netuid to get the metagraph of""", default=False, ) - metagraph_parser.add_argument( - "--no_prompt", - dest="no_prompt", - action="store_true", - help="""Set true to avoid prompting the user.""", - default=False, - ) - metagraph_parser.add_argument( - "--no_version_checking", - action="store_true", - help="""Set false to stop cli version checking""", - default=False, - ) + bittensor.subtensor.add_args(metagraph_parser) diff --git a/bittensor/_cli/commands/misc.py b/bittensor/commands/misc.py similarity index 87% rename from bittensor/_cli/commands/misc.py rename to bittensor/commands/misc.py index a28800b87c..9d62581201 100644 --- a/bittensor/_cli/commands/misc.py +++ b/bittensor/commands/misc.py @@ -36,7 +36,7 @@ def run(cli): os.system("pip install -e ~/.bittensor/bittensor/") @staticmethod - def check_config(config: "bittensor.Config"): + def check_config(config: "bittensor.config"): if not config.no_prompt: answer = Prompt.ask( "This will update the local bittensor package", @@ -50,19 +50,7 @@ def add_args(parser: argparse.ArgumentParser): update_parser = parser.add_parser( "update", add_help=False, help="""Update bittensor """ ) - update_parser.add_argument( - "--no_prompt", - dest="no_prompt", - action="store_true", - help="""Set true to skip prompt from update.""", - default=False, - ) - update_parser.add_argument( - "--no_version_checking", - action="store_true", - help="""Set false to stop cli version checking""", - default=False, - ) + bittensor.subtensor.add_args(update_parser) @@ -96,6 +84,7 @@ def run(cli): ), f"{subnet.emission_value / bittensor.utils.RAOPERTAO * 100:0.2f}%", f"{subnet.burn!s:8.8}", + f"{subnet.owner_ss58}", ) ) @@ -130,10 +119,12 @@ def run(cli): table.add_column("[overline white]TEMPO", style="white", justify="center") # table.add_column("[overline white]MODALITY", style='white') table.add_column("[overline white]CON_REQ", style="white", justify="center") + # table.add_column("[overline white]STAKE", style="green", justify="center") table.add_column( "[overline white]EMISSION", style="white", justify="center" ) # sums to 100% table.add_column("[overline white]BURN(\u03C4)", style="white") + table.add_column("[overline white]OWNER(\u03C4)", style="white") for row in rows: table.add_row(*row) @@ -141,7 +132,7 @@ def run(cli): bittensor.__console__.print(table) @staticmethod - def check_config(config: "bittensor.Config"): + def check_config(config: "bittensor.config"): pass @staticmethod @@ -149,11 +140,5 @@ def add_args(parser: argparse.ArgumentParser): list_subnets_parser = parser.add_parser( "list_subnets", help="""List all subnets on the network""" ) - list_subnets_parser.add_argument( - "--no_prompt", - dest="no_prompt", - action="store_true", - help="""Set true to avoid prompting the user.""", - default=False, - ) + bittensor.subtensor.add_args(list_subnets_parser) diff --git a/bittensor/commands/network.py b/bittensor/commands/network.py new file mode 100644 index 0000000000..f1a9a6f428 --- /dev/null +++ b/bittensor/commands/network.py @@ -0,0 +1,300 @@ +# The MIT License (MIT) +# Copyright © 2021 Yuma Rao + +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the “Software”), to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of +# the Software. + +# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# 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 time +import argparse +import bittensor +from . import defaults +from rich.prompt import Prompt +from rich.table import Table +from typing import List, Optional, Dict +from .utils import get_delegates_details, DelegatesDetails, check_netuid_set + +console = bittensor.__console__ + + +class RegisterSubnetworkCommand: + @staticmethod + def run(cli): + r"""Register a subnetwork""" + config = cli.config.copy() + wallet = bittensor.wallet(config=cli.config) + subtensor: bittensor.subtensor = bittensor.subtensor(config=config) + # Call register command. + subtensor.register_subnetwork( + wallet=wallet, + prompt=not cli.config.no_prompt, + ) + + @classmethod + def check_config(cls, config: "bittensor.config"): + if not config.is_set("wallet.name") and not config.no_prompt: + wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) + config.wallet.name = str(wallet_name) + + @classmethod + def add_args(cls, parser: argparse.ArgumentParser): + parser = parser.add_parser( + "create", + help="""Create a new bittensor subnetwork on this chain.""", + ) + + bittensor.wallet.add_args(parser) + bittensor.subtensor.add_args(parser) + + +class SubnetLockCostCommand: + @staticmethod + def run(cli): + r"""View locking cost of creating a new subnetwork""" + config = cli.config.copy() + subtensor: bittensor.subtensor = bittensor.subtensor(config=config) + try: + bittensor.__console__.print( + f"Subnet lock cost: [green]{bittensor.utils.balance.Balance( subtensor.get_subnet_burn_cost() )}[/green]" + ) + except Exception as e: + bittensor.__console__.print( + f"Subnet lock cost: [red]Failed to get subnet lock cost[/red]" + f"Error: {e}" + ) + + @classmethod + def check_config(cls, config: "bittensor.config"): + pass + + @classmethod + def add_args(cls, parser: argparse.ArgumentParser): + parser = parser.add_parser( + "lock_cost", + help=""" Return the lock cost to register a subnet""", + ) + + bittensor.subtensor.add_args(parser) + + +class SubnetListCommand: + @staticmethod + def run(cli): + r"""List all subnet netuids in the network.""" + subtensor = bittensor.subtensor(config=cli.config) + subnets: List[bittensor.SubnetInfo] = subtensor.get_all_subnets_info() + + rows = [] + total_neurons = 0 + delegate_info: Optional[Dict[str, DelegatesDetails]] = get_delegates_details( + url=bittensor.__delegates_details_url__ + ) + + for subnet in subnets: + total_neurons += subnet.max_n + rows.append( + ( + str(subnet.netuid), + str(subnet.subnetwork_n), + str(bittensor.utils.formatting.millify(subnet.max_n)), + f"{subnet.emission_value / bittensor.utils.RAOPERTAO * 100:0.2f}%", + str(subnet.tempo), + f"{subnet.burn!s:8.8}", + str(bittensor.utils.formatting.millify(subnet.difficulty)), + f"{delegate_info[subnet.owner_ss58].name if subnet.owner_ss58 in delegate_info else subnet.owner_ss58}", + ) + ) + table = Table( + show_footer=True, + width=cli.config.get("width", None), + pad_edge=True, + box=None, + show_edge=True, + ) + table.title = "[white]Subnets - {}".format(subtensor.network) + table.add_column( + "[overline white]NETUID", + str(len(subnets)), + footer_style="overline white", + style="bold green", + justify="center", + ) + table.add_column( + "[overline white]N", + str(total_neurons), + footer_style="overline white", + style="green", + justify="center", + ) + table.add_column("[overline white]MAX_N", style="white", justify="center") + table.add_column("[overline white]EMISSION", style="white", justify="center") + table.add_column("[overline white]TEMPO", style="white", justify="center") + table.add_column("[overline white]BURN", style="white", justify="center") + table.add_column("[overline white]POW", style="white", justify="center") + table.add_column("[overline white]SUDO", style="white") + for row in rows: + table.add_row(*row) + bittensor.__console__.print(table) + + @staticmethod + def check_config(config: "bittensor.config"): + pass + + @staticmethod + def add_args(parser: argparse.ArgumentParser): + list_subnets_parser = parser.add_parser( + "list", help="""List all subnets on the network""" + ) + bittensor.subtensor.add_args(list_subnets_parser) + + +HYPERPARAMS = { + "serving_rate_limit": "sudo_set_serving_rate_limit", + "weights_version": "sudo_set_weights_version_key", + "weights_rate_Limit": "sudo_set_weights_set_rate_limit", + "max_weight_limit": "sudo_set_max_weight_limit", + "immunity_period": "sudo_set_immunity_period", + "min_allowed_weights": "sudo_set_min_allowed_weights", + "activity_cutoff": "sudo_set_activity_cutoff", + "max_validators": "sudo_set_max_allowed_validators", +} + + +class SubnetSudoCommand: + @staticmethod + def run(cli): + r"""Set subnet hyperparameters.""" + config = cli.config.copy() + wallet = bittensor.wallet(config=cli.config) + subtensor: bittensor.subtensor = bittensor.subtensor(config=config) + print("\n") + SubnetHyperparamsCommand.run(cli) + if not config.is_set("param") and not config.no_prompt: + param = Prompt.ask("Enter hyperparameter", choices=HYPERPARAMS) + config.param = str(param) + if not config.is_set("value") and not config.no_prompt: + value = Prompt.ask("Enter new value") + config.value = value + + subtensor.set_hyperparameter( + wallet, + netuid=cli.config.netuid, + parameter=config.param, + value=config.value, + prompt=not cli.config.no_prompt, + ) + + @staticmethod + def check_config(config: "bittensor.config"): + if not config.is_set("wallet.name") and not config.no_prompt: + wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) + config.wallet.name = str(wallet_name) + + if not config.is_set("netuid") and not config.no_prompt: + check_netuid_set(config, bittensor.subtensor(config=config)) + + @staticmethod + def add_args(parser: argparse.ArgumentParser): + parser = parser.add_parser("set", help="""Set hyperparameters for a subnet""") + parser.add_argument( + "--netuid", dest="netuid", type=int, required=False, default=False + ) + parser.add_argument("--param", dest="param", type=str, required=False) + parser.add_argument("--value", dest="value", type=str, required=False) + + bittensor.wallet.add_args(parser) + bittensor.subtensor.add_args(parser) + + +class SubnetHyperparamsCommand: + @staticmethod + def run(cli): + r"""View hyperparameters of a subnetwork.""" + subtensor = bittensor.subtensor(config=cli.config) + subnet: bittensor.SubnetHyperparameters = subtensor.get_subnet_hyperparameters( + cli.config.netuid + ) + + table = Table( + show_footer=True, + width=cli.config.get("width", None), + pad_edge=True, + box=None, + show_edge=True, + ) + table.title = "[white]Subnet Hyperparameters - NETUID: {} - {}".format( + cli.config.netuid, subtensor.network + ) + table.add_column("[overline white]HYPERPARAMETER", style="bold white") + table.add_column("[overline white]VALUE", style="green") + + for param in subnet.__dict__: + table.add_row(" " + param, str(subnet.__dict__[param])) + + bittensor.__console__.print(table) + + @staticmethod + def check_config(config: "bittensor.config"): + if not config.is_set("netuid") and not config.no_prompt: + check_netuid_set(config, bittensor.subtensor(config=config)) + + @staticmethod + def add_args(parser: argparse.ArgumentParser): + parser = parser.add_parser( + "hyperparameters", help="""View subnet hyperparameters""" + ) + parser.add_argument( + "--netuid", dest="netuid", type=int, required=False, default=False + ) + bittensor.subtensor.add_args(parser) + + +class SubnetGetHyperparamsCommand: + @staticmethod + def run(cli): + r"""View hyperparameters of a subnetwork.""" + subtensor = bittensor.subtensor(config=cli.config) + subnet: bittensor.SubnetHyperparameters = subtensor.get_subnet_hyperparameters( + cli.config.netuid + ) + + table = Table( + show_footer=True, + width=cli.config.get("width", None), + pad_edge=True, + box=None, + show_edge=True, + ) + table.title = "[white]Subnet Hyperparameters - NETUID: {} - {}".format( + cli.config.netuid, subtensor.network + ) + table.add_column("[overline white]HYPERPARAMETER", style="white") + table.add_column("[overline white]VALUE", style="green") + + for param in subnet.__dict__: + table.add_row(param, str(subnet.__dict__[param])) + + bittensor.__console__.print(table) + + @staticmethod + def check_config(config: "bittensor.config"): + if not config.is_set("netuid") and not config.no_prompt: + check_netuid_set(config, bittensor.subtensor(config=config)) + + @staticmethod + def add_args(parser: argparse.ArgumentParser): + parser = parser.add_parser("get", help="""View subnet hyperparameters""") + parser.add_argument( + "--netuid", dest="netuid", type=int, required=False, default=False + ) + bittensor.subtensor.add_args(parser) diff --git a/bittensor/_cli/commands/overview.py b/bittensor/commands/overview.py similarity index 69% rename from bittensor/_cli/commands/overview.py rename to bittensor/commands/overview.py index 3f9e902b5b..877b4226fb 100644 --- a/bittensor/_cli/commands/overview.py +++ b/bittensor/commands/overview.py @@ -19,6 +19,7 @@ import bittensor from tqdm import tqdm from concurrent.futures import ProcessPoolExecutor +from collections import defaultdict from fuzzywuzzy import fuzz from rich.align import Align from rich.table import Table @@ -29,17 +30,18 @@ get_coldkey_wallets_for_path, get_all_wallets_for_path, ) +from . import defaults console = bittensor.__console__ class OverviewCommand: @staticmethod - def run(cli): + def run(cli: "bittensor.cli"): r"""Prints an overview for the wallet's colkey.""" console = bittensor.__console__ wallet = bittensor.wallet(config=cli.config) - subtensor: "bittensor.Subtensor" = bittensor.subtensor(config=cli.config) + subtensor: "bittensor.subtensor" = bittensor.subtensor(config=cli.config) all_hotkeys = [] total_balance = bittensor.Balance(0) @@ -95,7 +97,7 @@ def run(cli): return # Pull neuron info for all keys. - neurons: Dict[str, List[bittensor.NeuronInfoLite, str]] = {} + neurons: Dict[str, List[bittensor.NeuronInfoLite]] = {} block = subtensor.block netuids = subtensor.get_all_subnet_netuids() @@ -104,6 +106,22 @@ def run(cli): for netuid in netuids: neurons[str(netuid)] = [] + all_wallet_names = set([wallet.name for wallet in all_hotkeys]) + all_coldkey_wallets = [ + bittensor.wallet(name=wallet_name) for wallet_name in all_wallet_names + ] + + hotkey_coldkey_to_hotkey_wallet = {} + for hotkey_wallet in all_hotkeys: + if hotkey_wallet.hotkey.ss58_address not in hotkey_coldkey_to_hotkey_wallet: + hotkey_coldkey_to_hotkey_wallet[hotkey_wallet.hotkey.ss58_address] = {} + + hotkey_coldkey_to_hotkey_wallet[hotkey_wallet.hotkey.ss58_address][ + hotkey_wallet.coldkeypub.ss58_address + ] = hotkey_wallet + + all_hotkey_addresses = list(hotkey_coldkey_to_hotkey_wallet.keys()) + with console.status( ":satellite: Syncing with chain: [white]{}[/white] ...".format( cli.config.subtensor.get( @@ -111,17 +129,18 @@ def run(cli): ) ) ): - hotkey_addr_to_wallet = { - hotkey.hotkey.ss58_address: hotkey for hotkey in all_hotkeys - } - all_hotkey_addresses = list(hotkey_addr_to_wallet.keys()) + # Create a copy of the config without the parser and formatter_class. + ## This is needed to pass to the ProcessPoolExecutor, which cannot pickle the parser. + copy_config = cli.config.copy() + copy_config["__parser"] = None + copy_config["formatter_class"] = None # Pull neuron info for all keys. ## Max len(netuids) or 5 threads. with ProcessPoolExecutor(max_workers=max(len(netuids), 5)) as executor: results = executor.map( OverviewCommand._get_neurons_for_netuid, - [(cli.config, netuid, all_hotkey_addresses) for netuid in netuids], + [(copy_config, netuid, all_hotkey_addresses) for netuid in netuids], ) executor.shutdown(wait=True) # wait for all complete @@ -138,9 +157,100 @@ def run(cli): # Add neurons to overview. neurons[str(netuid)] = neurons_result + total_coldkey_stake_from_metagraph = defaultdict( + lambda: bittensor.Balance(0.0) + ) + for neuron_list in neurons.values(): + for neuron in neuron_list: + total_coldkey_stake_from_metagraph[ + neuron.coldkey + ] += neuron.stake_dict[neuron.coldkey] + + alerts_table = Table(show_header=True, header_style="bold magenta") + alerts_table.add_column("🥩 alert!") + + coldkeys_to_check = [] + for coldkey_wallet in all_coldkey_wallets: + # Check if we have any stake with hotkeys that are not registered. + total_coldkey_stake_from_chain = subtensor.get_total_stake_for_coldkey( + ss58_address=coldkey_wallet.coldkeypub.ss58_address + ) + difference = ( + total_coldkey_stake_from_chain + - total_coldkey_stake_from_metagraph[ + coldkey_wallet.coldkeypub.ss58_address + ] + ) + if difference == 0: + continue # We have all our stake registered. + + coldkeys_to_check.append(coldkey_wallet) + alerts_table.add_row( + "Found {} stake with coldkey {} that is not registered.".format( + difference, coldkey_wallet.coldkeypub.ss58_address + ) + ) + + if len(coldkeys_to_check) > 0: + # We have some stake that is not with a registered hotkey. + if "-1" not in neurons: + neurons["-1"] = [] + + # Use process pool to check each coldkey wallet for de-registered stake. + with ProcessPoolExecutor( + max_workers=max(len(coldkeys_to_check), 5) + ) as executor: + results = executor.map( + OverviewCommand._get_de_registered_stake_for_coldkey_wallet, + [ + (cli.config, all_hotkey_addresses, coldkey_wallet) + for coldkey_wallet in coldkeys_to_check + ], + ) + executor.shutdown(wait=True) # wait for all complete + + for result in results: + coldkey_wallet, de_registered_stake, err_msg = result + if err_msg is not None: + console.print(err_msg) + + if len(de_registered_stake) == 0: + continue # We have no de-registered stake with this coldkey. + + de_registered_neurons = [] + for hotkey_addr, our_stake in de_registered_stake: + # Make a neuron info lite for this hotkey and coldkey. + de_registered_neuron = bittensor.NeuronInfoLite._null_neuron() + de_registered_neuron.hotkey = hotkey_addr + de_registered_neuron.coldkey = ( + coldkey_wallet.coldkeypub.ss58_address + ) + de_registered_neuron.total_stake = bittensor.Balance(our_stake) + + de_registered_neurons.append(de_registered_neuron) + + # Add this hotkey to the wallets dict + wallet_ = bittensor.Wallet( + name=wallet, + ) + wallet_.hotkey = hotkey_addr + wallet.hotkey_str = hotkey_addr[ + :5 + ] # Max length of 5 characters + hotkey_coldkey_to_hotkey_wallet[hotkey_addr][ + coldkey_wallet.coldkeypub.ss58_address + ] = wallet_ + + # Add neurons to overview. + neurons["-1"].extend(de_registered_neurons) + # Setup outer table. grid = Table.grid(pad_edge=False) + # If there are any alerts, add them to the grid + if len(alerts_table.rows) > 0: + grid.add_row(alerts_table) + title: str = "" if not cli.config.get("all", d=None): title = "[bold white italic]Wallet - {}:{}".format( @@ -168,8 +278,8 @@ def run(cli): total_dividends = 0.0 total_emission = 0 - for nn, hotwallet_addr in neurons[str(netuid)]: - hotwallet = hotkey_addr_to_wallet[hotwallet_addr] + for nn in neurons[str(netuid)]: + hotwallet = hotkey_coldkey_to_hotkey_wallet[nn.hotkey][nn.coldkey] nn: bittensor.NeuronInfoLite uid = nn.uid active = nn.active @@ -214,15 +324,22 @@ def run(cli): total_emission += emission total_validator_trust += validator_trust - if not nn.hotkey in hotkeys_seen: - # Don't double count hotkeys or stake. - hotkeys_seen.add(nn.hotkey) + if not (nn.hotkey, nn.coldkey) in hotkeys_seen: + # Don't double count stake on hotkey-coldkey pairs. + hotkeys_seen.add((nn.hotkey, nn.coldkey)) total_stake += stake + + # netuid -1 are neurons that are de-registered. + if netuid != "-1": total_neurons += 1 + TABLE_DATA.append(row) # Add subnet header - grid.add_row(f"Subnet: [bold white]{netuid}[/bold white]") + if netuid == "-1": + grid.add_row(f"Deregistered Neurons") + else: + grid.add_row(f"Subnet: [bold white]{netuid}[/bold white]") table = Table( show_footer=False, @@ -389,10 +506,10 @@ def overview_sort_function(row): @staticmethod def _get_neurons_for_netuid( args_tuple: Tuple["bittensor.Config", int, List[str]] - ) -> Tuple[int, List[Tuple["bittensor.NeuronInfoLite", str]], Optional[str]]: + ) -> Tuple[int, List["bittensor.NeuronInfoLite"], Optional[str]]: subtensor_config, netuid, hot_wallets = args_tuple - result: List[Tuple["bittensor.NeuronInfoLite", str]] = [] + result: List["bittensor.NeuronInfoLite"] = [] try: subtensor = bittensor.subtensor(config=subtensor_config) @@ -406,24 +523,61 @@ def _get_neurons_for_netuid( uid = hotkey_to_neurons.get(hot_wallet_addr) if uid is not None: nn = all_neurons[uid] - result.append((nn, hot_wallet_addr)) + result.append(nn) except Exception as e: return netuid, [], "Error: {}".format(e) return netuid, result, None + @staticmethod + def _get_de_registered_stake_for_coldkey_wallet( + args_tuple, + ) -> Tuple[ + "bittensor.Wallet", List[Tuple[str, "bittensor.Balance"]], Optional[str] + ]: + subtensor_config, all_hotkey_addresses, coldkey_wallet = args_tuple + + # List of (hotkey_addr, our_stake) tuples. + result: List[Tuple[str, "bittensor.Balance"]] = [] + + try: + subtensor = bittensor.subtensor(config=subtensor_config) + + # Pull all stake for our coldkey + all_stake_info_for_coldkey = subtensor.get_stake_info_for_coldkey( + coldkey_ss58=coldkey_wallet.coldkeypub.ss58_address + ) + + ## Filter out hotkeys that are in our wallets + ## Filter out hotkeys that are delegates. + def _filter_stake_info(stake_info: "bittensor.StakeInfo") -> bool: + hotkey_addr, our_stake = stake_info + + if our_stake == 0: + return False # Skip hotkeys that we have no stake with. + if hotkey_addr in all_hotkey_addresses: + return False # Skip hotkeys that are in our wallets. + if subtensor.is_hotkey_delegate(hotkey_ss58=hotkey_addr): + return False # Skip hotkeys that are delegates, they show up in btcli my_delegates table. + + return True + + all_staked_hotkeys = filter(_filter_stake_info, all_stake_info_for_coldkey) + result = [ + (stake_info.hotkey, stake_info.stake) + for stake_info in all_staked_hotkeys + ] + + except Exception as e: + return coldkey_wallet, [], "Error: {}".format(e) + + return coldkey_wallet, result, None + @staticmethod def add_args(parser: argparse.ArgumentParser): overview_parser = parser.add_parser( "overview", help="""Show registered account overview.""" ) - overview_parser.add_argument( - "--no_prompt", - dest="no_prompt", - action="store_true", - help="""Set true to avoid prompting the user.""", - default=False, - ) overview_parser.add_argument( "--all", dest="all", @@ -487,25 +641,17 @@ def add_args(parser: argparse.ArgumentParser): help="""Set the netuid(s) to filter by.""", default=[], ) - overview_parser.add_argument( - "--no_version_checking", - action="store_true", - help="""Set false to stop cli version checking""", - default=False, - ) bittensor.wallet.add_args(overview_parser) bittensor.subtensor.add_args(overview_parser) @staticmethod - def check_config(config: "bittensor.Config"): + def check_config(config: "bittensor.config"): if ( not config.is_set("wallet.name") and not config.no_prompt and not config.get("all", d=None) ): - wallet_name = Prompt.ask( - "Enter wallet name", default=bittensor.defaults.wallet.name - ) + wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) config.wallet.name = str(wallet_name) if config.netuid != []: diff --git a/bittensor/commands/register.py b/bittensor/commands/register.py new file mode 100644 index 0000000000..834ab4e872 --- /dev/null +++ b/bittensor/commands/register.py @@ -0,0 +1,350 @@ +# The MIT License (MIT) +# Copyright © 2021 Yuma Rao + +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the “Software”), to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of +# the Software. + +# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# 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 sys +import argparse +import bittensor +from rich.prompt import Prompt, Confirm +from .utils import check_netuid_set, check_for_cuda_reg_config + +from . import defaults + +console = bittensor.__console__ + + +class RegisterCommand: + @staticmethod + def run(cli): + r"""Register neuron.""" + wallet = bittensor.wallet(config=cli.config) + subtensor = bittensor.subtensor(config=cli.config) + + # Verify subnet exists + if not subtensor.subnet_exists(netuid=cli.config.netuid): + bittensor.__console__.print( + f"[red]Subnet {cli.config.netuid} does not exist[/red]" + ) + sys.exit(1) + + subtensor.register( + wallet=wallet, + netuid=cli.config.netuid, + prompt=not cli.config.no_prompt, + TPB=cli.config.register.cuda.get("TPB", None), + update_interval=cli.config.register.get("update_interval", None), + num_processes=cli.config.register.get("num_processes", None), + cuda=cli.config.register.cuda.get( + "use_cuda", defaults.register.cuda.use_cuda + ), + dev_id=cli.config.register.cuda.get("dev_id", None), + output_in_place=cli.config.register.get( + "output_in_place", defaults.register.output_in_place + ), + log_verbose=cli.config.register.get("verbose", defaults.register.verbose), + ) + + @staticmethod + def add_args(parser: argparse.ArgumentParser): + register_parser = parser.add_parser( + "register", help="""Register a wallet to a network.""" + ) + register_parser.add_argument( + "--netuid", + type=int, + help="netuid for subnet to serve this neuron on", + default=argparse.SUPPRESS, + ) + register_parser.add_argument( + "--register.num_processes", + "-n", + dest="register.num_processes", + help="Number of processors to use for POW registration", + type=int, + default=defaults.register.num_processes, + ) + register_parser.add_argument( + "--register.update_interval", + "--register.cuda.update_interval", + "--cuda.update_interval", + "-u", + help="The number of nonces to process before checking for next block during registration", + type=int, + default=defaults.register.update_interval, + ) + register_parser.add_argument( + "--register.no_output_in_place", + "--no_output_in_place", + dest="register.output_in_place", + help="Whether to not ouput the registration statistics in-place. Set flag to disable output in-place.", + action="store_false", + required=False, + default=defaults.register.output_in_place, + ) + register_parser.add_argument( + "--register.verbose", + help="Whether to ouput the registration statistics verbosely.", + action="store_true", + required=False, + default=defaults.register.verbose, + ) + + ## Registration args for CUDA registration. + register_parser.add_argument( + "--register.cuda.use_cuda", + "--cuda", + "--cuda.use_cuda", + dest="register.cuda.use_cuda", + default=defaults.register.cuda.use_cuda, + help="""Set flag to use CUDA to register.""", + action="store_true", + required=False, + ) + register_parser.add_argument( + "--register.cuda.no_cuda", + "--no_cuda", + "--cuda.no_cuda", + dest="register.cuda.use_cuda", + default=not defaults.register.cuda.use_cuda, + help="""Set flag to not use CUDA for registration""", + action="store_false", + required=False, + ) + + register_parser.add_argument( + "--register.cuda.dev_id", + "--cuda.dev_id", + type=int, + nargs="+", + default=defaults.register.cuda.dev_id, + help="""Set the CUDA device id(s). Goes by the order of speed. (i.e. 0 is the fastest).""", + required=False, + ) + register_parser.add_argument( + "--register.cuda.TPB", + "--cuda.TPB", + type=int, + default=defaults.register.cuda.TPB, + help="""Set the number of Threads Per Block for CUDA.""", + required=False, + ) + + bittensor.wallet.add_args(register_parser) + bittensor.subtensor.add_args(register_parser) + + @staticmethod + def check_config(config: "bittensor.config"): + check_netuid_set(config, subtensor=bittensor.subtensor(config=config)) + + if not config.is_set("wallet.name") and not config.no_prompt: + wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) + config.wallet.name = str(wallet_name) + + if not config.is_set("wallet.hotkey") and not config.no_prompt: + hotkey = Prompt.ask("Enter hotkey name", default=defaults.wallet.hotkey) + config.wallet.hotkey = str(hotkey) + + if not config.no_prompt: + check_for_cuda_reg_config(config) + + +class RecycleRegisterCommand: + @staticmethod + def run(cli): + r"""Register neuron by recycling some TAO.""" + wallet = bittensor.wallet(config=cli.config) + subtensor = bittensor.subtensor(config=cli.config) + + # Verify subnet exists + if not subtensor.subnet_exists(netuid=cli.config.netuid): + bittensor.__console__.print( + f"[red]Subnet {cli.config.netuid} does not exist[/red]" + ) + sys.exit(1) + + # Check current recycle amount + current_recycle = subtensor.burn(netuid=cli.config.netuid) + balance = subtensor.get_balance(address=wallet.coldkeypub.ss58_address) + + # Check balance is sufficient + if balance < current_recycle: + bittensor.__console__.print( + f"[red]Insufficient balance {balance} to register neuron. Current recycle is {current_recycle} TAO[/red]" + ) + sys.exit(1) + + if not cli.config.no_prompt: + if ( + Confirm.ask( + f"Your balance is: [bold green]{balance}[/bold green]\nThe cost to register by recycle is [bold red]{current_recycle}[/bold red]\nDo you want to continue?", + default=False, + ) + == False + ): + sys.exit(1) + + subtensor.burned_register( + wallet=wallet, netuid=cli.config.netuid, prompt=not cli.config.no_prompt + ) + + @staticmethod + def add_args(parser: argparse.ArgumentParser): + recycle_register_parser = parser.add_parser( + "recycle_register", help="""Register a wallet to a network.""" + ) + recycle_register_parser.add_argument( + "--netuid", + type=int, + help="netuid for subnet to serve this neuron on", + default=argparse.SUPPRESS, + ) + + bittensor.wallet.add_args(recycle_register_parser) + bittensor.subtensor.add_args(recycle_register_parser) + + @staticmethod + def check_config(config: "bittensor.config"): + if ( + not config.is_set("subtensor.network") + and not config.is_set("subtensor.chain_endpoint") + and not config.no_prompt + ): + config.subtensor.network = Prompt.ask( + "Enter subtensor network", + choices=bittensor.__networks__, + default=defaults.subtensor.network, + ) + + check_netuid_set(config, subtensor=bittensor.subtensor(config=config)) + + if not config.is_set("wallet.name") and not config.no_prompt: + wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) + config.wallet.name = str(wallet_name) + + if not config.is_set("wallet.hotkey") and not config.no_prompt: + hotkey = Prompt.ask("Enter hotkey name", default=defaults.wallet.hotkey) + config.wallet.hotkey = str(hotkey) + + +class RunFaucetCommand: + @staticmethod + def run(cli): + r"""Register neuron.""" + wallet = bittensor.wallet(config=cli.config) + subtensor = bittensor.subtensor(config=cli.config) + subtensor.run_faucet( + wallet=wallet, + prompt=not cli.config.no_prompt, + TPB=cli.config.register.cuda.get("TPB", None), + update_interval=cli.config.register.get("update_interval", None), + num_processes=cli.config.register.get("num_processes", None), + cuda=cli.config.register.cuda.get( + "use_cuda", defaults.register.cuda.use_cuda + ), + dev_id=cli.config.register.cuda.get("dev_id", None), + output_in_place=cli.config.register.get( + "output_in_place", defaults.register.output_in_place + ), + log_verbose=cli.config.register.get("verbose", defaults.register.verbose), + ) + + @staticmethod + def add_args(parser: argparse.ArgumentParser): + run_faucet_parser = parser.add_parser( + "faucet", help="""Register a wallet to a network.""" + ) + run_faucet_parser.add_argument( + "--register.num_processes", + "-n", + dest="register.num_processes", + help="Number of processors to use for POW registration", + type=int, + default=defaults.register.num_processes, + ) + run_faucet_parser.add_argument( + "--register.update_interval", + "--register.cuda.update_interval", + "--cuda.update_interval", + "-u", + help="The number of nonces to process before checking for next block during registration", + type=int, + default=defaults.register.update_interval, + ) + run_faucet_parser.add_argument( + "--register.no_output_in_place", + "--no_output_in_place", + dest="register.output_in_place", + help="Whether to not ouput the registration statistics in-place. Set flag to disable output in-place.", + action="store_false", + required=False, + default=defaults.register.output_in_place, + ) + run_faucet_parser.add_argument( + "--register.verbose", + help="Whether to ouput the registration statistics verbosely.", + action="store_true", + required=False, + default=defaults.register.verbose, + ) + + ## Registration args for CUDA registration. + run_faucet_parser.add_argument( + "--register.cuda.use_cuda", + "--cuda", + "--cuda.use_cuda", + dest="register.cuda.use_cuda", + default=defaults.register.cuda.use_cuda, + help="""Set flag to use CUDA to register.""", + action="store_true", + required=False, + ) + run_faucet_parser.add_argument( + "--register.cuda.no_cuda", + "--no_cuda", + "--cuda.no_cuda", + dest="register.cuda.use_cuda", + default=not defaults.register.cuda.use_cuda, + help="""Set flag to not use CUDA for registration""", + action="store_false", + required=False, + ) + run_faucet_parser.add_argument( + "--register.cuda.dev_id", + "--cuda.dev_id", + type=int, + nargs="+", + default=defaults.register.cuda.dev_id, + help="""Set the CUDA device id(s). Goes by the order of speed. (i.e. 0 is the fastest).""", + required=False, + ) + run_faucet_parser.add_argument( + "--register.cuda.TPB", + "--cuda.TPB", + type=int, + default=defaults.register.cuda.TPB, + help="""Set the number of Threads Per Block for CUDA.""", + required=False, + ) + bittensor.wallet.add_args(run_faucet_parser) + bittensor.subtensor.add_args(run_faucet_parser) + + @staticmethod + def check_config(config: "bittensor.config"): + if not config.is_set("wallet.name") and not config.no_prompt: + wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) + config.wallet.name = str(wallet_name) + if not config.no_prompt: + check_for_cuda_reg_config(config) diff --git a/bittensor/commands/root.py b/bittensor/commands/root.py new file mode 100644 index 0000000000..427e91e2b7 --- /dev/null +++ b/bittensor/commands/root.py @@ -0,0 +1,212 @@ +# The MIT License (MIT) +# Copyright © 2021 Yuma Rao + +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the “Software”), to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of +# the Software. + +# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# 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 sys +import re +import torch +import typing +import argparse +import bittensor +from typing import List, Optional, Dict +from rich.prompt import Prompt, Confirm +from rich.table import Table +from .utils import get_delegates_details, DelegatesDetails + +from . import defaults + +console = bittensor.__console__ + + +class RootRegisterCommand: + @staticmethod + def run(cli): + r"""Register to root network.""" + wallet = bittensor.wallet(config=cli.config) + subtensor = bittensor.subtensor(config=cli.config) + + subtensor.root_register(wallet=wallet, prompt=not cli.config.no_prompt) + + @staticmethod + def add_args(parser: argparse.ArgumentParser): + parser = parser.add_parser( + "register", help="""Register a wallet to the root network.""" + ) + + bittensor.wallet.add_args(parser) + bittensor.subtensor.add_args(parser) + + @staticmethod + def check_config(config: "bittensor.config"): + if not config.is_set("wallet.name") and not config.no_prompt: + wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) + config.wallet.name = str(wallet_name) + + if not config.is_set("wallet.hotkey") and not config.no_prompt: + hotkey = Prompt.ask("Enter hotkey name", default=defaults.wallet.hotkey) + config.wallet.hotkey = str(hotkey) + + +class RootList: + @staticmethod + def run(cli): + r"""List the root network""" + subtensor = bittensor.subtensor(config=cli.config) + console.print( + ":satellite: Syncing with chain: [white]{}[/white] ...".format( + subtensor.network + ) + ) + + senate_members = subtensor.get_senate_members() + root_neurons: typing.List[bittensor.NeuronInfoLite] = subtensor.neurons_lite( + netuid=0 + ) + delegate_info: Optional[Dict[str, DelegatesDetails]] = get_delegates_details( + url=bittensor.__delegates_details_url__ + ) + + table = Table(show_footer=False) + table.title = "[white]Root Network" + table.add_column( + "[overline white]UID", + footer_style="overline white", + style="rgb(50,163,219)", + no_wrap=True, + ) + table.add_column( + "[overline white]NAME", + footer_style="overline white", + style="rgb(50,163,219)", + no_wrap=True, + ) + table.add_column( + "[overline white]ADDRESS", + footer_style="overline white", + style="yellow", + no_wrap=True, + ) + table.add_column( + "[overline white]STAKE(\u03C4)", + footer_style="overline white", + justify="right", + style="green", + no_wrap=True, + ) + table.add_column( + "[overline white]SENATOR", + footer_style="overline white", + style="green", + no_wrap=True, + ) + table.show_footer = True + + for neuron_data in root_neurons: + table.add_row( + str(neuron_data.uid), + delegate_info[neuron_data.hotkey].name + if neuron_data.hotkey in delegate_info + else "", + neuron_data.hotkey, + "{:.5f}".format( + float(subtensor.get_total_stake_for_hotkey(neuron_data.hotkey)) + ), + "Yes" if neuron_data.hotkey in senate_members else "No", + ) + + table.box = None + table.pad_edge = False + table.width = None + bittensor.__console__.print(table) + + @staticmethod + def add_args(parser: argparse.ArgumentParser): + parser = parser.add_parser("list", help="""List the root network""") + bittensor.subtensor.add_args(parser) + + @staticmethod + def check_config(config: "bittensor.config"): + pass + + +class RootSetWeightsCommand: + @staticmethod + def run(cli): + r"""Set weights for root network.""" + wallet = bittensor.wallet(config=cli.config) + subtensor = bittensor.subtensor(config=cli.config) + subnets: List[bittensor.SubnetInfo] = subtensor.get_all_subnets_info() + + # Get values if not set. + if not cli.config.is_set("netuids"): + example = ( + ", ".join(map(str, [subnet.netuid for subnet in subnets][:3])) + " ..." + ) + cli.config.netuids = Prompt.ask(f"Enter netuids (e.g. {example})") + + if not cli.config.is_set("weights"): + example = ( + ", ".join( + map( + str, + [ + "{:.2f}".format(float(1 / len(subnets))) + for subnet in subnets + ][:3], + ) + ) + + " ..." + ) + 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 + ) + weights = torch.tensor( + list(map(float, re.split(r"[ ,]+", cli.config.weights))), + dtype=torch.float32, + ) + + # Run the set weights operation. + subtensor.root_set_weights( + wallet=wallet, + netuids=netuids, + weights=weights, + version_key=0, + prompt=not cli.config.no_prompt, + wait_for_finalization=True, + wait_for_inclusion=True, + ) + + @staticmethod + def add_args(parser: argparse.ArgumentParser): + parser = parser.add_parser("weights", help="""Set weights for root network.""") + parser.add_argument("--netuids", dest="netuids", type=str, required=False) + parser.add_argument("--weights", dest="weights", type=str, required=False) + + bittensor.wallet.add_args(parser) + bittensor.subtensor.add_args(parser) + + @staticmethod + def check_config(config: "bittensor.config"): + if not config.is_set("wallet.name") and not config.no_prompt: + wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) + config.wallet.name = str(wallet_name) + + if not config.is_set("wallet.hotkey") and not config.no_prompt: + hotkey = Prompt.ask("Enter hotkey name", default=defaults.wallet.hotkey) + config.wallet.hotkey = str(hotkey) diff --git a/bittensor/_cli/commands/senate.py b/bittensor/commands/senate.py similarity index 78% rename from bittensor/_cli/commands/senate.py rename to bittensor/commands/senate.py index f8b48a8af8..ba4165ce47 100644 --- a/bittensor/_cli/commands/senate.py +++ b/bittensor/commands/senate.py @@ -22,6 +22,7 @@ from rich.table import Table from typing import List, Union, Optional, Dict, Tuple from .utils import get_delegates_details, DelegatesDetails +from . import defaults console = bittensor.__console__ @@ -31,7 +32,7 @@ class SenateCommand: def run(cli): r"""View Bittensor's governance protocol proposals""" config = cli.config.copy() - subtensor: bittensor.Subtensor = bittensor.subtensor(config=config) + subtensor: bittensor.subtensor = bittensor.subtensor(config=config) console.print( ":satellite: Syncing with chain: [white]{}[/white] ...".format( @@ -74,7 +75,7 @@ def run(cli): console.print(table) @classmethod - def check_config(cls, config: "bittensor.Config"): + def check_config(cls, config: "bittensor.config"): None @classmethod @@ -82,19 +83,7 @@ def add_args(cls, parser: argparse.ArgumentParser): senate_parser = parser.add_parser( "senate", help="""View senate and it's members""" ) - senate_parser.add_argument( - "--no_version_checking", - action="store_true", - help="""Set false to stop cli version checking""", - default=False, - ) - senate_parser.add_argument( - "--no_prompt", - dest="no_prompt", - action="store_true", - help="""Set true to avoid prompting the user.""", - default=False, - ) + bittensor.wallet.add_args(senate_parser) bittensor.subtensor.add_args(senate_parser) @@ -151,11 +140,11 @@ class ProposalsCommand: def run(cli): r"""View Bittensor's governance protocol proposals""" config = cli.config.copy() - subtensor: bittensor.Subtensor = bittensor.subtensor(config=config) + subtensor: bittensor.subtensor = bittensor.subtensor(config=config) console.print( ":satellite: Syncing with chain: [white]{}[/white] ...".format( - cli.config.subtensor.network + subtensor.network ) ) @@ -219,7 +208,7 @@ def run(cli): console.print(table) @classmethod - def check_config(cls, config: "bittensor.Config"): + def check_config(cls, config: "bittensor.config"): None @classmethod @@ -227,19 +216,7 @@ def add_args(cls, parser: argparse.ArgumentParser): proposals_parser = parser.add_parser( "proposals", help="""View active triumvirate proposals and their status""" ) - proposals_parser.add_argument( - "--no_version_checking", - action="store_true", - help="""Set false to stop cli version checking""", - default=False, - ) - proposals_parser.add_argument( - "--no_prompt", - dest="no_prompt", - action="store_true", - help="""Set true to avoid prompting the user.""", - default=False, - ) + bittensor.wallet.add_args(proposals_parser) bittensor.subtensor.add_args(proposals_parser) @@ -249,7 +226,7 @@ class ShowVotesCommand: def run(cli): r"""View Bittensor's governance protocol proposals active votes""" config = cli.config.copy() - subtensor: bittensor.Subtensor = bittensor.subtensor(config=config) + subtensor: bittensor.subtensor = bittensor.subtensor(config=config) console.print( ":satellite: Syncing with chain: [white]{}[/white] ...".format( @@ -297,7 +274,7 @@ def run(cli): console.print(table) @classmethod - def check_config(cls, config: "bittensor.Config"): + def check_config(cls, config: "bittensor.config"): if config.proposal_hash == "" and not config.no_prompt: proposal_hash = Prompt.ask("Enter proposal hash") config.proposal_hash = str(proposal_hash) @@ -307,19 +284,6 @@ def add_args(cls, parser: argparse.ArgumentParser): show_votes_parser = parser.add_parser( "proposal_votes", help="""View an active proposal's votes by address.""" ) - show_votes_parser.add_argument( - "--no_version_checking", - action="store_true", - help="""Set false to stop cli version checking""", - default=False, - ) - show_votes_parser.add_argument( - "--no_prompt", - dest="no_prompt", - action="store_true", - help="""Set true to avoid prompting the user.""", - default=False, - ) show_votes_parser.add_argument( "--proposal", dest="proposal_hash", @@ -338,7 +302,7 @@ def run(cli): r"""Register to participate in Bittensor's governance protocol proposals""" config = cli.config.copy() wallet = bittensor.wallet(config=cli.config) - subtensor: bittensor.Subtensor = bittensor.subtensor(config=config) + subtensor: bittensor.subtensor = bittensor.subtensor(config=config) # Unlock the wallet. wallet.hotkey @@ -364,17 +328,13 @@ def run(cli): subtensor.register_senate(wallet=wallet, prompt=not cli.config.no_prompt) @classmethod - def check_config(cls, config: "bittensor.Config"): + def check_config(cls, config: "bittensor.config"): if not config.is_set("wallet.name") and not config.no_prompt: - wallet_name = Prompt.ask( - "Enter wallet name", default=bittensor.defaults.wallet.name - ) + wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) config.wallet.name = str(wallet_name) if not config.is_set("wallet.hotkey") and not config.no_prompt: - hotkey = Prompt.ask( - "Enter hotkey name", default=bittensor.defaults.wallet.hotkey - ) + hotkey = Prompt.ask("Enter hotkey name", default=defaults.wallet.hotkey) config.wallet.hotkey = str(hotkey) @classmethod @@ -383,19 +343,7 @@ def add_args(cls, parser: argparse.ArgumentParser): "senate_register", help="""Register as a senate member to participate in proposals""", ) - senate_register_parser.add_argument( - "--no_version_checking", - action="store_true", - help="""Set false to stop cli version checking""", - default=False, - ) - senate_register_parser.add_argument( - "--no_prompt", - dest="no_prompt", - action="store_true", - help="""Set true to avoid prompting the user.""", - default=False, - ) + bittensor.wallet.add_args(senate_register_parser) bittensor.subtensor.add_args(senate_register_parser) @@ -406,7 +354,7 @@ def run(cli): r"""Discard membership in Bittensor's governance protocol proposals""" config = cli.config.copy() wallet = bittensor.wallet(config=cli.config) - subtensor: bittensor.Subtensor = bittensor.subtensor(config=config) + subtensor: bittensor.subtensor = bittensor.subtensor(config=config) # Unlock the wallet. wallet.hotkey @@ -423,17 +371,13 @@ def run(cli): subtensor.leave_senate(wallet=wallet, prompt=not cli.config.no_prompt) @classmethod - def check_config(cls, config: "bittensor.Config"): + def check_config(cls, config: "bittensor.config"): if not config.is_set("wallet.name") and not config.no_prompt: - wallet_name = Prompt.ask( - "Enter wallet name", default=bittensor.defaults.wallet.name - ) + wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) config.wallet.name = str(wallet_name) if not config.is_set("wallet.hotkey") and not config.no_prompt: - hotkey = Prompt.ask( - "Enter hotkey name", default=bittensor.defaults.wallet.hotkey - ) + hotkey = Prompt.ask("Enter hotkey name", default=defaults.wallet.hotkey) config.wallet.hotkey = str(hotkey) @classmethod @@ -442,19 +386,7 @@ def add_args(cls, parser: argparse.ArgumentParser): "senate_leave", help="""Discard senate membership in the governance protocol""", ) - senate_leave_parser.add_argument( - "--no_version_checking", - action="store_true", - help="""Set false to stop cli version checking""", - default=False, - ) - senate_leave_parser.add_argument( - "--no_prompt", - dest="no_prompt", - action="store_true", - help="""Set true to avoid prompting the user.""", - default=False, - ) + bittensor.wallet.add_args(senate_leave_parser) bittensor.subtensor.add_args(senate_leave_parser) @@ -465,7 +397,7 @@ def run(cli): r"""Vote in Bittensor's governance protocol proposals""" config = cli.config.copy() wallet = bittensor.wallet(config=cli.config) - subtensor: bittensor.Subtensor = bittensor.subtensor(config=config) + subtensor: bittensor.subtensor = bittensor.subtensor(config=config) proposal_hash = cli.config.proposal_hash if len(proposal_hash) == 0: @@ -501,17 +433,13 @@ def run(cli): ) @classmethod - def check_config(cls, config: "bittensor.Config"): + def check_config(cls, config: "bittensor.config"): if not config.is_set("wallet.name") and not config.no_prompt: - wallet_name = Prompt.ask( - "Enter wallet name", default=bittensor.defaults.wallet.name - ) + wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) config.wallet.name = str(wallet_name) if not config.is_set("wallet.hotkey") and not config.no_prompt: - hotkey = Prompt.ask( - "Enter hotkey name", default=bittensor.defaults.wallet.hotkey - ) + hotkey = Prompt.ask("Enter hotkey name", default=defaults.wallet.hotkey) config.wallet.hotkey = str(hotkey) if config.proposal_hash == "" and not config.no_prompt: @@ -523,19 +451,6 @@ def add_args(cls, parser: argparse.ArgumentParser): vote_parser = parser.add_parser( "senate_vote", help="""Vote on an active proposal by hash.""" ) - vote_parser.add_argument( - "--no_version_checking", - action="store_true", - help="""Set false to stop cli version checking""", - default=False, - ) - vote_parser.add_argument( - "--no_prompt", - dest="no_prompt", - action="store_true", - help="""Set true to avoid prompting the user.""", - default=False, - ) vote_parser.add_argument( "--proposal", dest="proposal_hash", diff --git a/bittensor/_cli/commands/stake.py b/bittensor/commands/stake.py similarity index 53% rename from bittensor/_cli/commands/stake.py rename to bittensor/commands/stake.py index d147695f4f..acf5f76a49 100644 --- a/bittensor/_cli/commands/stake.py +++ b/bittensor/commands/stake.py @@ -23,6 +23,7 @@ from bittensor.utils.balance import Balance from typing import List, Union, Optional, Dict, Tuple from .utils import get_hotkey_wallets_for_wallet +from . import defaults console = bittensor.__console__ @@ -33,7 +34,7 @@ def run(cli): r"""Stake token of amount to hotkey(s).""" config = cli.config.copy() wallet = bittensor.wallet(config=config) - subtensor: bittensor.Subtensor = bittensor.subtensor(config=config) + subtensor: bittensor.subtensor = bittensor.subtensor(config=config) # Get the hotkey_names (if any) and the hotkey_ss58s. hotkeys_to_stake_to: List[Tuple[Optional[str], str]] = [] @@ -170,11 +171,9 @@ def run(cli): ) @classmethod - def check_config(cls, config: "bittensor.Config"): + def check_config(cls, config: "bittensor.config"): if not config.is_set("wallet.name") and not config.no_prompt: - wallet_name = Prompt.ask( - "Enter wallet name", default=bittensor.defaults.wallet.name - ) + wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) config.wallet.name = str(wallet_name) if ( @@ -183,9 +182,7 @@ def check_config(cls, config: "bittensor.Config"): and not config.wallet.get("all_hotkeys") and not config.wallet.get("hotkeys") ): - hotkey = Prompt.ask( - "Enter hotkey name", default=bittensor.defaults.wallet.hotkey - ) + hotkey = Prompt.ask("Enter hotkey name", default=defaults.wallet.hotkey) config.wallet.hotkey = str(hotkey) # Get amount. @@ -196,7 +193,7 @@ def check_config(cls, config: "bittensor.Config"): ): if not Confirm.ask( "Stake all Tao from account: [bold]'{}'[/bold]?".format( - config.wallet.get("name", bittensor.defaults.wallet.name) + config.wallet.get("name", defaults.wallet.name) ) ): amount = Prompt.ask("Enter Tao amount to stake") @@ -215,13 +212,7 @@ def check_config(cls, config: "bittensor.Config"): @classmethod def add_args(cls, parser: argparse.ArgumentParser): stake_parser = parser.add_parser( - "stake", help="""Stake to your hotkey accounts.""" - ) - stake_parser.add_argument( - "--no_version_checking", - action="store_true", - help="""Set false to stop cli version checking""", - default=False, + "add", help="""Add stake to your hotkey accounts from your coldkey.""" ) stake_parser.add_argument("--all", dest="stake_all", action="store_true") stake_parser.add_argument("--uid", dest="uid", type=int, required=False) @@ -235,13 +226,6 @@ def add_args(cls, parser: argparse.ArgumentParser): default=None, help="""Specify the maximum amount of Tao to have staked in each hotkey.""", ) - stake_parser.add_argument( - "--no_prompt", - dest="no_prompt", - action="store_true", - help="""Set true to avoid prompting the user.""", - default=False, - ) stake_parser.add_argument( "--hotkeys", "--exclude_hotkeys", @@ -264,3 +248,249 @@ def add_args(cls, parser: argparse.ArgumentParser): ) bittensor.wallet.add_args(stake_parser) bittensor.subtensor.add_args(stake_parser) + + +### Stake list. +import json +import argparse +import bittensor +from tqdm import tqdm +from rich.table import Table +from rich.prompt import Prompt +from typing import Dict, Union, List, Tuple +from concurrent.futures import ThreadPoolExecutor +from .utils import check_netuid_set, get_delegates_details, DelegatesDetails +from . import defaults + +console = bittensor.__console__ + +import os +import bittensor +from typing import List, Tuple, Optional, Dict + + +def _get_coldkey_wallets_for_path(path: str) -> List["bittensor.wallet"]: + try: + wallet_names = next(os.walk(os.path.expanduser(path)))[1] + return [bittensor.wallet(path=path, name=name) for name in wallet_names] + except StopIteration: + # No wallet files found. + wallets = [] + return wallets + + +def _get_hotkey_wallets_for_wallet(wallet) -> List["bittensor.wallet"]: + hotkey_wallets = [] + hotkeys_path = wallet.path + "/" + wallet.name + "/hotkeys" + try: + hotkey_files = next(os.walk(os.path.expanduser(hotkeys_path)))[2] + except StopIteration: + hotkey_files = [] + for hotkey_file_name in hotkey_files: + try: + hotkey_for_name = bittensor.wallet( + path=wallet.path, name=wallet.name, hotkey=hotkey_file_name + ) + if ( + hotkey_for_name.hotkey_file.exists_on_device() + and not hotkey_for_name.hotkey_file.is_encrypted() + ): + hotkey_wallets.append(hotkey_for_name) + except Exception: + pass + return hotkey_wallets + + +class StakeShow: + @staticmethod + def run(cli): + r"""Show all stake accounts.""" + if cli.config.get("all", d=False) == True: + wallets = _get_coldkey_wallets_for_path(cli.config.wallet.path) + else: + wallets = [bittensor.wallet(config=cli.config)] + registered_delegate_info: Optional[ + Dict[str, DelegatesDetails] + ] = get_delegates_details(url=bittensor.__delegates_details_url__) + + def get_stake_accounts(wallet) -> Dict[str, Dict[str, Union[str, Balance]]]: + """Get stake account details for the given wallet. + + Args: + wallet: The wallet object to fetch the stake account details for. + + Returns: + A dictionary mapping SS58 addresses to their respective stake account details. + """ + subtensor = bittensor.subtensor(config=cli.config) + + wallet_stake_accounts = {} + + # Get this wallet's coldkey balance. + cold_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address) + + # Populate the stake accounts with local hotkeys data. + wallet_stake_accounts.update(get_stakes_from_hotkeys(subtensor, wallet)) + + # Populate the stake accounts with delegations data. + wallet_stake_accounts.update(get_stakes_from_delegates(subtensor, wallet)) + + return { + "name": wallet.name, + "balance": cold_balance, + "accounts": wallet_stake_accounts, + } + + def get_stakes_from_hotkeys( + subtensor, wallet + ) -> Dict[str, Dict[str, Union[str, Balance]]]: + """Fetch stakes from hotkeys for the provided wallet. + + Args: + wallet: The wallet object to fetch the stakes for. + + Returns: + A dictionary of stakes related to hotkeys. + """ + hotkeys = get_hotkey_wallets_for_wallet(wallet) + stakes = {} + for hot in hotkeys: + emission = sum( + [ + n.emission + for n in subtensor.get_all_neurons_for_pubkey( + hot.hotkey.ss58_address + ) + ] + ) + hotkey_stake = subtensor.get_stake_for_coldkey_and_hotkey( + hotkey_ss58=hot.hotkey.ss58_address, + coldkey_ss58=wallet.coldkeypub.ss58_address, + ) + stakes[hot.hotkey.ss58_address] = { + "name": hot.hotkey_str, + "stake": hotkey_stake, + "rate": emission, + } + return stakes + + def get_stakes_from_delegates( + subtensor, wallet + ) -> Dict[str, Dict[str, Union[str, Balance]]]: + """Fetch stakes from delegates for the provided wallet. + + Args: + wallet: The wallet object to fetch the stakes for. + + Returns: + A dictionary of stakes related to delegates. + """ + delegates = subtensor.get_delegated( + coldkey_ss58=wallet.coldkeypub.ss58_address + ) + stakes = {} + for dele, staked in delegates: + for nom in dele.nominators: + if nom[0] == wallet.coldkeypub.ss58_address: + delegate_name = ( + registered_delegate_info[dele.hotkey_ss58].name + if dele.hotkey_ss58 in registered_delegate_info + else dele.hotkey_ss58 + ) + stakes[dele.hotkey_ss58] = { + "name": delegate_name, + "stake": nom[1], + "rate": dele.total_daily_return.tao + * (nom[1] / dele.total_stake.tao), + } + return stakes + + def get_all_wallet_accounts( + wallets, + ) -> List[Dict[str, Dict[str, Union[str, Balance]]]]: + """Fetch stake accounts for all provided wallets using a ThreadPool. + + Args: + wallets: List of wallets to fetch the stake accounts for. + + Returns: + A list of dictionaries, each dictionary containing stake account details for each wallet. + """ + + accounts = [] + # Create a progress bar using tqdm + with tqdm(total=len(wallets), desc="Fetching accounts", ncols=100) as pbar: + # Using a ThreadPool to fetch accounts. + with ThreadPoolExecutor() as executor: + for account in executor.map(get_stake_accounts, wallets): + accounts.append(account) + pbar.update() + return accounts + + accounts = get_all_wallet_accounts(wallets) + + total_stake = 0 + total_balance = 0 + total_rate = 0 + for acc in accounts: + total_balance += acc["balance"].tao + for key, value in acc["accounts"].items(): + total_stake += value["stake"].tao + total_rate += float(value["rate"]) + table = Table(show_footer=True, pad_edge=False, box=None, expand=False) + table.add_column( + "[overline white]Coldkey", footer_style="overline white", style="bold white" + ) + table.add_column( + "[overline white]Balance", + "\u03C4{:.5f}".format(total_balance), + footer_style="overline white", + style="green", + ) + table.add_column( + "[overline white]Account", footer_style="overline white", style="blue" + ) + table.add_column( + "[overline white]Stake", + "\u03C4{:.5f}".format(total_stake), + footer_style="overline white", + style="green", + ) + table.add_column( + "[overline white]Rate", + "\u03C4{:.5f}/d".format(total_rate), + footer_style="overline white", + style="green", + ) + for acc in accounts: + table.add_row(acc["name"], acc["balance"], "", "") + for key, value in acc["accounts"].items(): + table.add_row( + "", "", value["name"], value["stake"], str(value["rate"]) + "/d" + ) + bittensor.__console__.print(table) + + @staticmethod + def check_config(config: "bittensor.config"): + if ( + not config.get("all", d=None) + and not config.is_set("wallet.name") + and not config.no_prompt + ): + wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) + config.wallet.name = str(wallet_name) + + @staticmethod + def add_args(parser: argparse.ArgumentParser): + list_parser = parser.add_parser( + "show", help="""List all stake accounts for wallet.""" + ) + list_parser.add_argument( + "--all", + action="store_true", + help="""Check all coldkey wallets.""", + default=False, + ) + + bittensor.wallet.add_args(list_parser) + bittensor.subtensor.add_args(list_parser) diff --git a/bittensor/_cli/commands/transfer.py b/bittensor/commands/transfer.py similarity index 83% rename from bittensor/_cli/commands/transfer.py rename to bittensor/commands/transfer.py index fb384f1da4..b0c51486f0 100644 --- a/bittensor/_cli/commands/transfer.py +++ b/bittensor/commands/transfer.py @@ -20,6 +20,7 @@ import argparse import bittensor from rich.prompt import Prompt +from . import defaults console = bittensor.__console__ @@ -39,15 +40,13 @@ def run(cli): ) @staticmethod - def check_config(config: "bittensor.Config"): + def check_config(config: "bittensor.config"): if not config.is_set("wallet.name") and not config.no_prompt: - wallet_name = Prompt.ask( - "Enter wallet name", default=bittensor.defaults.wallet.name - ) + wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) config.wallet.name = str(wallet_name) # Get destination. - if not config.dest: + if not config.dest and not config.no_prompt: dest = Prompt.ask("Enter destination public key: (ss58 or ed2519)") if not bittensor.utils.is_valid_bittensor_address_or_public_key(dest): sys.exit() @@ -56,8 +55,8 @@ def check_config(config: "bittensor.Config"): # Get current balance and print to user. if not config.no_prompt: - wallet = bittensor.wallet(config) - subtensor = bittensor.subtensor(config) + wallet = bittensor.wallet(config=config) + subtensor = bittensor.subtensor(config=config) with bittensor.__console__.status(":satellite: Checking Balance..."): account_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address) bittensor.__console__.print( @@ -90,22 +89,10 @@ def add_args(parser: argparse.ArgumentParser): transfer_parser = parser.add_parser( "transfer", help="""Transfer Tao between accounts.""" ) - transfer_parser.add_argument( - "--no_version_checking", - action="store_true", - help="""Set false to stop cli version checking""", - default=False, - ) transfer_parser.add_argument("--dest", dest="dest", type=str, required=False) transfer_parser.add_argument( "--amount", dest="amount", type=float, required=False ) - transfer_parser.add_argument( - "--no_prompt", - dest="no_prompt", - action="store_true", - help="""Set true to avoid prompting the user.""", - default=False, - ) + bittensor.wallet.add_args(transfer_parser) bittensor.subtensor.add_args(transfer_parser) diff --git a/bittensor/_cli/commands/unstake.py b/bittensor/commands/unstake.py similarity index 91% rename from bittensor/_cli/commands/unstake.py rename to bittensor/commands/unstake.py index bb1e9600e6..f7e8d41201 100644 --- a/bittensor/_cli/commands/unstake.py +++ b/bittensor/commands/unstake.py @@ -22,17 +22,16 @@ from bittensor.utils.balance import Balance from typing import List, Union, Optional, Tuple from .utils import get_hotkey_wallets_for_wallet +from . import defaults console = bittensor.__console__ class UnStakeCommand: @classmethod - def check_config(cls, config: "bittensor.Config"): + def check_config(cls, config: "bittensor.config"): if not config.is_set("wallet.name") and not config.no_prompt: - wallet_name = Prompt.ask( - "Enter wallet name", default=bittensor.defaults.wallet.name - ) + wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) config.wallet.name = str(wallet_name) if ( @@ -42,9 +41,7 @@ def check_config(cls, config: "bittensor.Config"): and not config.get("all_hotkeys") and not config.get("hotkeys") ): - hotkey = Prompt.ask( - "Enter hotkey name", default=bittensor.defaults.wallet.hotkey - ) + hotkey = Prompt.ask("Enter hotkey name", default=defaults.wallet.hotkey) config.wallet.hotkey = str(hotkey) # Get amount. @@ -81,19 +78,11 @@ def check_config(cls, config: "bittensor.Config"): @staticmethod def add_args(command_parser): unstake_parser = command_parser.add_parser( - "unstake", help="""Unstake from hotkey accounts.""" - ) - unstake_parser.add_argument( - "--no_version_checking", - action="store_true", - help="""Set false to stop cli version checking""", - default=False, + "remove", + help="""Remove stake from your hotkey accounts into their coldkey accounts.""", ) unstake_parser.add_argument( - "--all", - dest="unstake_all", - action="store_true", - default=False, + "--all", dest="unstake_all", action="store_true", default=False ) unstake_parser.add_argument( "--amount", dest="amount", type=float, required=False @@ -110,13 +99,6 @@ def add_args(command_parser): default=None, help="""Specify the maximum amount of Tao to have staked in each hotkey.""", ) - unstake_parser.add_argument( - "--no_prompt", - dest="no_prompt", - action="store_true", - help="""Set true to avoid prompting the user.""", - default=False, - ) unstake_parser.add_argument( "--hotkeys", "--exclude_hotkeys", @@ -145,7 +127,7 @@ def run(cli): r"""Unstake token of amount from hotkey(s).""" config = cli.config.copy() wallet = bittensor.wallet(config=config) - subtensor: bittensor.Subtensor = bittensor.subtensor(config=cli.config) + subtensor: bittensor.subtensor = bittensor.subtensor(config=cli.config) # Get the hotkey_names (if any) and the hotkey_ss58s. hotkeys_to_unstake_from: List[Tuple[Optional[str], str]] = [] diff --git a/bittensor/_cli/commands/utils.py b/bittensor/commands/utils.py similarity index 90% rename from bittensor/_cli/commands/utils.py rename to bittensor/commands/utils.py index 2538ddcae0..b54053622a 100644 --- a/bittensor/_cli/commands/utils.py +++ b/bittensor/commands/utils.py @@ -23,6 +23,7 @@ from rich.prompt import Confirm, Prompt, PromptBase import requests from dataclasses import dataclass +from . import defaults console = bittensor.__console__ @@ -43,8 +44,8 @@ def check_choice(self, value: str) -> bool: def check_netuid_set( - config: "bittensor.Config", - subtensor: "bittensor.Subtensor", + config: "bittensor.config", + subtensor: "bittensor.subtensor", allow_none: bool = False, ): if subtensor.network != "nakamoto": @@ -60,32 +61,34 @@ def check_netuid_set( "Enter netuid", choices=all_netuids, default=str(all_netuids[0]) ) else: - netuid = str(bittensor.defaults.netuid) if not allow_none else "None" + netuid = str(defaults.netuid) if not allow_none else "None" else: netuid = config.netuid if isinstance(netuid, str) and netuid.lower() in ["none"] and allow_none: config.netuid = None else: + if isinstance(netuid, list): + netuid = netuid[0] try: config.netuid = int(netuid) - except ValueError: + except: raise ValueError('netuid must be an integer or "None" (if applicable)') -def check_for_cuda_reg_config(config: "bittensor.Config") -> None: +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(): if not config.no_prompt: - if config.subtensor.register.cuda.get("use_cuda") == None: # flag not set + if config.register.cuda.get("use_cuda") == None: # flag not set # Ask about cuda registration only if a CUDA device is available. cuda = Confirm.ask("Detected CUDA device, use CUDA for registration?\n") - config.subtensor.register.cuda.use_cuda = cuda + config.register.cuda.use_cuda = cuda # Only ask about which CUDA device if the user has more than one CUDA device. if ( - config.subtensor.register.cuda.use_cuda - and config.subtensor.register.cuda.get("dev_id") is None + config.register.cuda.use_cuda + and config.register.cuda.get("dev_id") is None ): devices: List[str] = [str(x) for x in range(torch.cuda.device_count())] device_names: List[str] = [ @@ -119,13 +122,11 @@ def check_for_cuda_reg_config(config: "bittensor.Config") -> None: ) ) sys.exit(1) - config.subtensor.register.cuda.dev_id = dev_id + config.register.cuda.dev_id = dev_id else: # flag was not set, use default value. - if config.subtensor.register.cuda.get("use_cuda") is None: - config.subtensor.register.cuda.use_cuda = ( - bittensor.defaults.subtensor.register.cuda.use_cuda - ) + if config.register.cuda.get("use_cuda") is None: + config.register.cuda.use_cuda = defaults.register.cuda.use_cuda def get_hotkey_wallets_for_wallet(wallet) -> List["bittensor.wallet"]: diff --git a/bittensor/_cli/commands/wallets.py b/bittensor/commands/wallets.py similarity index 75% rename from bittensor/_cli/commands/wallets.py rename to bittensor/commands/wallets.py index b8a163ae60..782e2dff0b 100644 --- a/bittensor/_cli/commands/wallets.py +++ b/bittensor/commands/wallets.py @@ -19,8 +19,9 @@ import bittensor import os import sys -from rich.prompt import Prompt -from typing import Optional +from rich.prompt import Prompt, Confirm +from typing import Optional, List +from . import defaults class RegenColdkeyCommand: @@ -49,11 +50,9 @@ def run(cli): ) @staticmethod - def check_config(config: "bittensor.Config"): + def check_config(config: "bittensor.config"): if not config.is_set("wallet.name") and not config.no_prompt: - wallet_name = Prompt.ask( - "Enter wallet name", default=bittensor.defaults.wallet.name - ) + wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) config.wallet.name = str(wallet_name) if ( config.mnemonic == None @@ -78,12 +77,6 @@ def add_args(parser: argparse.ArgumentParser): regen_coldkey_parser = parser.add_parser( "regen_coldkey", help="""Regenerates a coldkey from a passed value""" ) - regen_coldkey_parser.add_argument( - "--no_version_checking", - action="store_true", - help="""Set false to stop cli version checking""", - default=False, - ) regen_coldkey_parser.add_argument( "--mnemonic", required=False, @@ -121,13 +114,6 @@ def add_args(parser: argparse.ArgumentParser): action="store_false", help="""Set off protects the generated bittensor key with a password.""", ) - regen_coldkey_parser.add_argument( - "--no_prompt", - dest="no_prompt", - action="store_true", - help="""Set true to avoid prompting the user.""", - default=False, - ) regen_coldkey_parser.add_argument( "--overwrite_coldkey", default=False, @@ -149,11 +135,9 @@ def run(cli): ) @staticmethod - def check_config(config: "bittensor.Config"): + def check_config(config: "bittensor.config"): if not config.is_set("wallet.name") and not config.no_prompt: - wallet_name = Prompt.ask( - "Enter wallet name", default=bittensor.defaults.wallet.name - ) + wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) config.wallet.name = str(wallet_name) if config.ss58_address == None and config.public_key_hex == None: prompt_answer = Prompt.ask( @@ -176,12 +160,6 @@ def add_args(parser: argparse.ArgumentParser): "regen_coldkeypub", help="""Regenerates a coldkeypub from the public part of the coldkey.""", ) - regen_coldkeypub_parser.add_argument( - "--no_version_checking", - action="store_true", - help="""Set false to stop cli version checking""", - default=False, - ) regen_coldkeypub_parser.add_argument( "--public_key", "--pubkey", @@ -201,13 +179,6 @@ def add_args(parser: argparse.ArgumentParser): type=str, help="The ss58 address of the coldkey to regen e.g. 5ABCD ...", ) - regen_coldkeypub_parser.add_argument( - "--no_prompt", - dest="no_prompt", - action="store_true", - help="""Set true to avoid prompting the user.""", - default=False, - ) regen_coldkeypub_parser.add_argument( "--overwrite_coldkeypub", default=False, @@ -244,17 +215,13 @@ def run(cli): ) @staticmethod - def check_config(config: "bittensor.Config"): + def check_config(config: "bittensor.config"): if not config.is_set("wallet.name") and not config.no_prompt: - wallet_name = Prompt.ask( - "Enter wallet name", default=bittensor.defaults.wallet.name - ) + wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) config.wallet.name = str(wallet_name) if not config.is_set("wallet.hotkey") and not config.no_prompt: - hotkey = Prompt.ask( - "Enter hotkey name", default=bittensor.defaults.wallet.hotkey - ) + hotkey = Prompt.ask("Enter hotkey name", default=defaults.wallet.hotkey) config.wallet.hotkey = str(hotkey) if ( config.mnemonic == None @@ -279,12 +246,6 @@ def add_args(parser: argparse.ArgumentParser): regen_hotkey_parser = parser.add_parser( "regen_hotkey", help="""Regenerates a hotkey from a passed mnemonic""" ) - regen_hotkey_parser.add_argument( - "--no_version_checking", - action="store_true", - help="""Set false to stop cli version checking""", - default=False, - ) regen_hotkey_parser.add_argument( "--mnemonic", required=False, @@ -322,13 +283,6 @@ def add_args(parser: argparse.ArgumentParser): action="store_false", help="""Set off protects the generated bittensor key with a password.""", ) - regen_hotkey_parser.add_argument( - "--no_prompt", - dest="no_prompt", - action="store_true", - help="""Set true to avoid prompting the user.""", - default=False, - ) regen_hotkey_parser.add_argument( "--overwrite_hotkey", dest="overwrite_hotkey", @@ -351,17 +305,13 @@ def run(cli): ) @staticmethod - def check_config(config: "bittensor.Config"): + def check_config(config: "bittensor.config"): if not config.is_set("wallet.name") and not config.no_prompt: - wallet_name = Prompt.ask( - "Enter wallet name", default=bittensor.defaults.wallet.name - ) + wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) config.wallet.name = str(wallet_name) if not config.is_set("wallet.hotkey") and not config.no_prompt: - hotkey = Prompt.ask( - "Enter hotkey name", default=bittensor.defaults.wallet.hotkey - ) + hotkey = Prompt.ask("Enter hotkey name", default=defaults.wallet.hotkey) config.wallet.hotkey = str(hotkey) @staticmethod @@ -370,12 +320,6 @@ def add_args(parser: argparse.ArgumentParser): "new_hotkey", help="""Creates a new hotkey (for running a miner) under the specified path.""", ) - new_hotkey_parser.add_argument( - "--no_version_checking", - action="store_true", - help="""Set false to stop cli version checking""", - default=False, - ) new_hotkey_parser.add_argument( "--n_words", type=int, @@ -396,13 +340,6 @@ def add_args(parser: argparse.ArgumentParser): action="store_false", help="""Set off protects the generated bittensor key with a password.""", ) - new_hotkey_parser.add_argument( - "--no_prompt", - dest="no_prompt", - action="store_true", - help="""Set true to avoid prompting the user.""", - default=False, - ) new_hotkey_parser.add_argument( "--overwrite_hotkey", action="store_false", @@ -424,11 +361,9 @@ def run(cli): ) @staticmethod - def check_config(config: "bittensor.Config"): + def check_config(config: "bittensor.config"): if not config.is_set("wallet.name") and not config.no_prompt: - wallet_name = Prompt.ask( - "Enter wallet name", default=bittensor.defaults.wallet.name - ) + wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) config.wallet.name = str(wallet_name) @staticmethod @@ -438,10 +373,64 @@ def add_args(parser: argparse.ArgumentParser): help="""Creates a new coldkey (for containing balance) under the specified path. """, ) new_coldkey_parser.add_argument( - "--no_version_checking", + "--n_words", + type=int, + choices=[12, 15, 18, 21, 24], + default=12, + help="""The number of words representing the mnemonic. i.e. horse cart dog ... x 24""", + ) + new_coldkey_parser.add_argument( + "--use_password", + dest="use_password", action="store_true", - help="""Set false to stop cli version checking""", + help="""Set true to protect the generated bittensor key with a password.""", + default=True, + ) + new_coldkey_parser.add_argument( + "--no_password", + dest="use_password", + action="store_false", + help="""Set off protects the generated bittensor key with a password.""", + ) + new_coldkey_parser.add_argument( + "--overwrite_coldkey", + action="store_false", default=False, + help="""Overwrite the old coldkey with the newly generated coldkey""", + ) + bittensor.wallet.add_args(new_coldkey_parser) + bittensor.subtensor.add_args(new_coldkey_parser) + + +class WalletCreateCommand: + def run(cli): + r"""Creates a new coldkey and hotkey under this wallet.""" + wallet = bittensor.wallet(config=cli.config) + wallet.create_new_coldkey( + n_words=cli.config.n_words, + use_password=cli.config.use_password, + overwrite=cli.config.overwrite_coldkey, + ) + wallet.create_new_hotkey( + n_words=cli.config.n_words, + use_password=False, + overwrite=cli.config.overwrite_hotkey, + ) + + @staticmethod + def check_config(config: "bittensor.config"): + if not config.is_set("wallet.name") and not config.no_prompt: + wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) + config.wallet.name = str(wallet_name) + if not config.is_set("wallet.hotkey") and not config.no_prompt: + hotkey = Prompt.ask("Enter hotkey name", default=defaults.wallet.hotkey) + config.wallet.hotkey = str(hotkey) + + @staticmethod + def add_args(parser: argparse.ArgumentParser): + new_coldkey_parser = parser.add_parser( + "create", + help="""Creates a new coldkey (for containing balance) under the specified path. """, ) new_coldkey_parser.add_argument( "--n_words", @@ -464,17 +453,76 @@ def add_args(parser: argparse.ArgumentParser): help="""Set off protects the generated bittensor key with a password.""", ) new_coldkey_parser.add_argument( - "--no_prompt", - dest="no_prompt", - action="store_true", - help="""Set true to avoid prompting the user.""", + "--overwrite_coldkey", + action="store_false", default=False, + help="""Overwrite the old coldkey with the newly generated coldkey""", ) new_coldkey_parser.add_argument( - "--overwrite_coldkey", + "--overwrite_hotkey", action="store_false", default=False, - help="""Overwrite the old coldkey with the newly generated coldkey""", + help="""Overwrite the old hotkey with the newly generated hotkey""", ) bittensor.wallet.add_args(new_coldkey_parser) bittensor.subtensor.add_args(new_coldkey_parser) + + +def _get_coldkey_wallets_for_path(path: str) -> List["bittensor.wallet"]: + """Get all coldkey wallet names from path.""" + try: + wallet_names = next(os.walk(os.path.expanduser(path)))[1] + return [bittensor.wallet(path=path, name=name) for name in wallet_names] + except StopIteration: + # No wallet files found. + wallets = [] + return wallets + + +class UpdateWalletCommand: + @staticmethod + def run(cli): + """Check if any of the wallets needs an update.""" + config = cli.config.copy() + if config.get("all", d=False) == True: + wallets = _get_coldkey_wallets_for_path(config.wallet.path) + else: + wallets = [bittensor.wallet(config=config)] + + for wallet in wallets: + print("\n===== ", wallet, " =====") + wallet.coldkey_file.check_and_update_encryption() + + @staticmethod + def add_args(parser: argparse.ArgumentParser): + update_wallet_parser = parser.add_parser( + "update", help="""Delegate Stake to an account.""" + ) + update_wallet_parser.add_argument("--all", action="store_true") + update_wallet_parser.add_argument( + "--no_prompt", + dest="no_prompt", + action="store_true", + help="""Set true to avoid prompting the user.""", + default=False, + ) + bittensor.wallet.add_args(update_wallet_parser) + bittensor.subtensor.add_args(update_wallet_parser) + + @staticmethod + def check_config(config: "bittensor.Config"): + if config.get("all", d=False) == False: + if not config.no_prompt: + if Confirm.ask("Do you want to update all legacy wallets?"): + config["all"] = True + + # Ask the user to specify the wallet if the wallet name is not clear. + if ( + config.get("all", d=False) == False + and config.wallet.get("name") == bittensor.defaults.wallet.name + and not config.no_prompt + ): + wallet_name = Prompt.ask( + "Enter wallet name", default=bittensor.defaults.wallet.name + ) + config.wallet.name = str(wallet_name) diff --git a/bittensor/config.py b/bittensor/config.py new file mode 100644 index 0000000000..b402b30ddd --- /dev/null +++ b/bittensor/config.py @@ -0,0 +1,375 @@ +""" +Implementation of the config class, which manages the config of different bittensor modules. +""" +# The MIT License (MIT) +# Copyright © 2021 Yuma Rao +# Copyright © 2022 Opentensor Foundation + +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the “Software”), to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of +# the Software. + +# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# 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 os +import sys +import yaml +import copy +from copy import deepcopy +from munch import DefaultMunch +from typing import List, Optional, Dict, Any, TypeVar, Type +import argparse + + +class InvalidConfigFile(Exception): + """In place of YAMLError""" + + pass + + +class config(DefaultMunch): + """ + Implementation of the config class, which manages the config of different bittensor modules. + """ + + __is_set: Dict[str, bool] + + r""" Translates the passed parser into a nested Bittensor config. + Args: + parser (argparse.ArgumentParser): + Command line parser object. + strict (bool): + If true, the command line arguments are strictly parsed. + args (list of str): + Command line arguments. + default (Optional[Any]): + Default value for the Config. Defaults to None. + This default will be returned for attributes that are undefined. + Returns: + config (bittensor.config): + Nested config object created from parser arguments. + """ + + def __init__( + self, + parser: argparse.ArgumentParser = None, + args: Optional[List[str]] = None, + strict: bool = False, + default: Optional[Any] = None, + ) -> None: + super().__init__(default) + + self["__is_set"] = {} + + if parser == None: + return None + + # Optionally add config specific arguments + try: + parser.add_argument( + "--config", + type=str, + help="If set, defaults are overridden by passed file.", + ) + except: + # this can fail if --config has already been added. + pass + + try: + parser.add_argument( + "--strict", + action="store_true", + help="""If flagged, config will check that only exact arguments have been set.""", + default=False, + ) + except: + # this can fail if --strict has already been added. + pass + + try: + parser.add_argument( + "--no_version_checking", + action="store_true", + help="Set true to stop cli version checking.", + default=False, + ) + except: + # this can fail if --no_version_checking has already been added. + pass + + try: + parser.add_argument( + "--no_prompt", + dest="no_prompt", + action="store_true", + help="Set true to stop cli from prompting the user.", + default=False, + ) + except: + # this can fail if --no_version_checking has already been added. + pass + + # Get args from argv if not passed in. + if args == None: + args = sys.argv[1:] + + # 1.1 Optionally load defaults if the --config is set. + try: + config_file_path = ( + str(os.getcwd()) + + "/" + + vars(parser.parse_known_args(args)[0])["config"] + ) + except Exception as e: + config_file_path = None + + # Parse args not strict + config_params = config.__parse_args__(args=args, parser=parser, strict=False) + + # 2. Optionally check for --strict + ## strict=True when passed in OR when --strict is set + strict = config_params.strict or strict + + if config_file_path != None: + config_file_path = os.path.expanduser(config_file_path) + try: + with open(config_file_path) as f: + params_config = yaml.safe_load(f) + print("Loading config defaults from: {}".format(config_file_path)) + parser.set_defaults(**params_config) + except Exception as e: + print("Error in loading: {} using default parser settings".format(e)) + + # 2. Continue with loading in params. + params = config.__parse_args__(args=args, parser=parser, strict=strict) + + _config = self + + # Splits params and add to config + config.__split_params__(params=params, _config=_config) + + # Make the is_set map + _config["__is_set"] = {} + + ## Reparse args using default of unset + parser_no_defaults = copy.deepcopy(parser) + + # Only command as the arg, else no args + default_param_args = ( + [_config.get("command")] + if _config.get("command") != None and _config.get("subcommand") == None + else [] + ) + if _config.get("command") != None and _config.get("subcommand") != None: + default_param_args = [_config.get("command"), _config.get("subcommand")] + + ## Get all args by name + default_params = parser.parse_args(args=default_param_args) + + all_default_args = default_params.__dict__.keys() | [] + ## Make a dict with keys as args and values as argparse.SUPPRESS + defaults_as_suppress = {key: argparse.SUPPRESS for key in all_default_args} + ## Set the defaults to argparse.SUPPRESS, should remove them from the namespace + parser_no_defaults.set_defaults(**defaults_as_suppress) + parser_no_defaults._defaults.clear() # Needed for quirk of argparse + + ### Check for subparsers and do the same + if parser_no_defaults._subparsers != None: + for action in parser_no_defaults._subparsers._actions: + # Should only be the "command" subparser action + if isinstance(action, argparse._SubParsersAction): + # Set the defaults to argparse.SUPPRESS, should remove them from the namespace + # Each choice is the keyword for a command, we need to set the defaults for each of these + ## Note: we also need to clear the _defaults dict for each, this is a quirk of argparse + cmd_parser: argparse.ArgumentParser + for cmd_parser in action.choices.values(): + # If this choice is also a subparser, set defaults recursively + if cmd_parser._subparsers: + for action in cmd_parser._subparsers._actions: + # Should only be the "command" subparser action + if isinstance(action, argparse._SubParsersAction): + cmd_parser: argparse.ArgumentParser + for cmd_parser in action.choices.values(): + cmd_parser.set_defaults(**defaults_as_suppress) + cmd_parser._defaults.clear() # Needed for quirk of argparse + else: + cmd_parser.set_defaults(**defaults_as_suppress) + cmd_parser._defaults.clear() # Needed for quirk of argparse + + ## Reparse the args, but this time with the defaults as argparse.SUPPRESS + params_no_defaults = config.__parse_args__( + args=args, parser=parser_no_defaults, strict=strict + ) + + ## Diff the params and params_no_defaults to get the is_set map + _config["__is_set"] = { + arg_key: True + for arg_key in [ + k + for k, _ in filter( + lambda kv: kv[1] != argparse.SUPPRESS, + params_no_defaults.__dict__.items(), + ) + ] + } + + @staticmethod + def __split_params__(params: argparse.Namespace, _config: "config"): + # Splits params on dot syntax i.e neuron.axon_port and adds to _config + for arg_key, arg_val in params.__dict__.items(): + split_keys = arg_key.split(".") + head = _config + keys = split_keys + while len(keys) > 1: + if ( + hasattr(head, keys[0]) and head[keys[0]] != None + ): # Needs to be Config + head = getattr(head, keys[0]) + keys = keys[1:] + else: + head[keys[0]] = config() + head = head[keys[0]] + keys = keys[1:] + if len(keys) == 1: + head[keys[0]] = arg_val + + @staticmethod + def __parse_args__( + args: List[str], parser: argparse.ArgumentParser = None, strict: bool = False + ) -> argparse.Namespace: + """Parses the passed args use the passed parser. + Args: + args (List[str]): + List of arguments to parse. + parser (argparse.ArgumentParser): + Command line parser object. + strict (bool): + If true, the command line arguments are strictly parsed. + Returns: + Namespace: + Namespace object created from parser arguments. + """ + if not strict: + params, unrecognized = parser.parse_known_args(args=args) + params_list = list(params.__dict__) + # bug within argparse itself, does not correctly set value for boolean flags + for unrec in unrecognized: + if unrec.startswith("--") and unrec[2:] in params_list: + # Set the missing boolean value to true + setattr(params, unrec[2:], True) + else: + params = parser.parse_args(args=args) + + return params + + def __deepcopy__(self, memo) -> "config": + _default = self.__default__ + + config_state = self.__getstate__() + config_copy = config() + memo[id(self)] = config_copy + + config_copy.__setstate__(config_state) + config_copy.__default__ = _default + + config_copy["__is_set"] = deepcopy(self["__is_set"], memo) + + return config_copy + + def __repr__(self) -> str: + return self.__str__() + + def __str__(self) -> str: + # remove the parser and is_set map from the visible config + visible = self.toDict() + visible.pop("__parser", None) + visible.pop("__is_set", None) + return "\n" + yaml.dump(visible) + + def copy(self) -> "config": + return copy.deepcopy(self) + + def to_string(self, items) -> str: + """Get string from items""" + return "\n" + yaml.dump(items.toDict()) + + def update_with_kwargs(self, kwargs): + """Add config to self""" + for key, val in kwargs.items(): + self[key] = val + + @classmethod + def _merge(cls, a, b): + """Merge two configurations recursively. + If there is a conflict, the value from the second configuration will take precedence. + """ + for key in b: + if key in a: + if isinstance(a[key], dict) and isinstance(b[key], dict): + a[key] = cls._merge(a[key], b[key]) + else: + a[key] = b[key] + else: + a[key] = b[key] + return a + + def merge(self, b): + """ + Merges the current config with another config. + + Args: + b: Another config to merge. + """ + self = self._merge(self, b) + + @classmethod + def merge_all(cls, configs: List["config"]) -> "config": + """ + Merge all configs in the list into one config. + If there is a conflict, the value from the last configuration in the list will take precedence. + + Args: + configs (list of config): + List of configs to be merged. + + Returns: + config: + Merged config object. + """ + result = cls() + for cfg in configs: + result.merge(cfg) + return result + + def is_set(self, param_name: str) -> bool: + """ + Returns a boolean indicating whether the parameter has been set or is still the default. + """ + if param_name not in self.get("__is_set"): + return False + else: + return self.get("__is_set")[param_name] + + +T = TypeVar("T", bound="DefaultConfig") + + +class DefaultConfig(config): + """ + A Config with a set of default values. + """ + + @classmethod + def default(cls: Type[T]) -> T: + """ + Get default config. + """ + raise NotImplementedError("Function default is not implemented.") diff --git a/bittensor/dendrite.py b/bittensor/dendrite.py new file mode 100644 index 0000000000..e831fa0d02 --- /dev/null +++ b/bittensor/dendrite.py @@ -0,0 +1,428 @@ +# The MIT License (MIT) +# Copyright © 2021 Yuma Rao +# Copyright © 2022 Opentensor Foundation +# Copyright © 2023 Opentensor Technologies Inc + +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the “Software”), to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of +# the Software. + +# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# 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. + +from __future__ import annotations + +import asyncio +import uuid +import time +import torch +import aiohttp +import bittensor +from fastapi import Response +from typing import Union, Optional, List, Union + + +class dendrite(torch.nn.Module): + """ + The Dendrite class, inheriting from PyTorch's Module 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 + to send requests to those endpoint to recieve inputs. + + This class includes a wallet or keypair used for signing messages, and methods for making + HTTP requests to the network servers. It also provides functionalities such as logging + network requests and processing server responses. + + Attributes: + keypair: The wallet or keypair used for signing messages. + + Methods: + __str__(): Returns a string representation of the Dendrite object. + __repr__(): Returns a string representation of the Dendrite object, acting as a fallback + for __str__(). + + Example: + >>> dendrite_obj = dendrite(wallet = bittensor.wallet() ) + >>> print(dendrite_obj) + >>> d( ) # ping axon + >>> d( [] ) # ping multiple + >>> d( bittensor.axon(), bittensor.Synapse ) + """ + + def __init__( + self, wallet: Optional[Union[bittensor.wallet, bittensor.keypair]] = None + ): + """ + Initializes the Dendrite object, setting up essential properties. + + Args: + wallet (Optional[Union['bittensor.wallet', 'bittensor.keypair']], optional): + The user's wallet or keypair used for signing messages. Defaults to None, + in which case a new bittensor.wallet().hotkey is generated and used. + """ + # Initialize the parent class + super(dendrite, self).__init__() + + # Unique identifier for the instance + self.uuid = str(uuid.uuid1()) + + # Get the external IP + self.external_ip = bittensor.utils.networking.get_external_ip() + + # If a wallet or keypair is provided, use its hotkey. If not, generate a new one. + self.keypair = ( + wallet.hotkey if isinstance(wallet, bittensor.wallet) else wallet + ) or bittensor.wallet().hotkey + + self.synapse_history: list = [] + + self._session: aiohttp.ClientSession = None + + @property + async def session(self) -> aiohttp.ClientSession: + if self._session is None: + self._session = aiohttp.ClientSession() + return self._session + + async def close_session(self): + if self._session: + await self._session.close() + self._session = None + + def query( + self, *args, **kwargs + ) -> Union[bittensor.Synapse, List[bittensor.Synapse]]: + """ + Makes a synchronous request to multiple target Axons and returns the server responses. + + Args: + axons (Union[List[Union['bittensor.AxonInfo', 'bittensor.axon']], Union['bittensor.AxonInfo', 'bittensor.axon']]): + The list of target Axon information. + synapse (bittensor.Synapse, optional): The Synapse object. Defaults to bittensor.Synapse(). + timeout (float, optional): The request timeout duration in seconds. + Defaults to 12.0 seconds. + Returns: + Union[bittensor.Synapse, List[bittensor.Synapse]]: If a single target axon is provided, + returns the response from that axon. If multiple target axons are provided, + returns a list of responses from all target axons. + """ + try: + loop = asyncio.get_event_loop() + return loop.run_until_complete(self.forward(*args, **kwargs)) + except: + new_loop = asyncio.new_event_loop() + asyncio.set_event_loop(new_loop) + result = loop.run_until_complete(self.forward(*args, **kwargs)) + new_loop.close() + return result + + async def forward( + self, + axons: Union[ + List[Union[bittensor.AxonInfo, bittensor.axon]], + Union[bittensor.AxonInfo, bittensor.axon], + ], + synapse: bittensor.Synapse = bittensor.Synapse(), + timeout: float = 12, + deserialize: bool = True, + run_async: bool = True, + ) -> bittensor.Synapse: + """ + Makes asynchronous requests to multiple target Axons and returns the server responses. + + Args: + axons (Union[List[Union['bittensor.AxonInfo', 'bittensor.axon']], Union['bittensor.AxonInfo', 'bittensor.axon']]): + The list of target Axon information. + synapse (bittensor.Synapse, optional): The Synapse object. Defaults to bittensor.Synapse(). + timeout (float, optional): The request timeout duration in seconds. + Defaults to 12.0 seconds. + + Returns: + Union[bittensor.Synapse, List[bittensor.Synapse]]: If a single target axon is provided, + returns the response from that axon. If multiple target axons are provided, + returns a list of responses from all target axons. + """ + is_list = True + # If a single axon is provided, wrap it in a list for uniform processing + if not isinstance(axons, list): + is_list = False + axons = [axons] + + # This asynchronous function is used to send queries to all axons. + async def query_all_axons() -> List[bittensor.Synapse]: + # If the 'run_async' flag is not set, the code runs synchronously. + if not run_async: + # Create an empty list to hold the responses from all axons. + all_responses = [] + # Loop through each axon in the 'axons' list. + for target_axon in axons: + # The response from each axon is then appended to the 'all_responses' list. + all_responses.append( + await self.call( + target_axon=target_axon, + synapse=synapse.copy(), + timeout=timeout, + deserialize=deserialize, + ) + ) + # The function then returns a list of responses from all axons. + return all_responses + else: + # Here we build a list of coroutines without awaiting them. + coroutines = [ + self.call( + target_axon=target_axon, + synapse=synapse.copy(), + timeout=timeout, + deserialize=deserialize, + ) + for target_axon in axons + ] + # 'asyncio.gather' is a method which takes multiple coroutines and runs them in parallel. + all_responses = await asyncio.gather(*coroutines) + # The function then returns a list of responses from all axons. + return all_responses + + # Run all requests concurrently and get the responses + responses = await query_all_axons() + + # Return the single response if only one axon was targeted, else return all responses + if len(responses) == 1 and not is_list: + return responses[0] + else: + return responses + + async def call( + self, + target_axon: Union[bittensor.AxonInfo, bittensor.axon], + synapse: bittensor.Synapse = bittensor.Synapse(), + timeout: float = 12.0, + deserialize: bool = True, + ) -> bittensor.Synapse: + """ + Makes an asynchronous request to the target Axon, processes the server + response and returns the updated Synapse. + + Args: + target_axon (Union['bittensor.AxonInfo', 'bittensor.axon']): The target Axon information. + synapse (bittensor.Synapse, optional): The Synapse object. Defaults to bittensor.Synapse(). + timeout (float, optional): The request timeout duration in seconds. + Defaults to 12.0 seconds. + deserialize (bool, optional): Whether to deserialize the returned Synapse. + Defaults to True. + + Returns: + bittensor.Synapse: The updated Synapse object after processing server response. + """ + + # Record start time + start_time = time.time() + target_axon = ( + target_axon.info() + if isinstance(target_axon, bittensor.axon) + else target_axon + ) + + # Build request endpoint from the synapse class + request_name = synapse.__class__.__name__ + endpoint = ( + f"0.0.0.0:{str(target_axon.port)}" + if target_axon.ip == str(self.external_ip) + else f"{target_axon.ip}:{str(target_axon.port)}" + ) + url = f"http://{endpoint}/{request_name}" + + # Preprocess synapse for making a request + synapse = self.preprocess_synapse_for_request(target_axon, synapse, timeout) + + try: + # Log outgoing request + bittensor.logging.debug( + f"dendrite | --> | {synapse.get_total_size()} B | {synapse.name} | {synapse.axon.hotkey} | {synapse.axon.ip}:{str(synapse.axon.port)} | 0 | Success" + ) + + # Make the HTTP POST request + async with (await self.session).post( + url, + headers=synapse.to_headers(), + json=synapse.dict(), + timeout=timeout, + ) as response: + if ( + response.headers.get("Content-Type", "").lower() + == "text/event-stream".lower() + ): # identify streaming response + await synapse.process_streaming_response( + response + ) # process the entire streaming response + json_response = synapse.extract_response_json(response) + else: + json_response = await response.json() + + # Process the server response + self.process_server_response(response, json_response, synapse) + + # Set process time and log the response + synapse.dendrite.process_time = str(time.time() - start_time) + bittensor.logging.debug( + f"dendrite | <-- | {synapse.get_total_size()} B | {synapse.name} | {synapse.axon.hotkey} | {synapse.axon.ip}:{str(synapse.axon.port)} | {synapse.axon.status_code} | {synapse.axon.status_message}" + ) + + except aiohttp.ClientConnectorError as e: + synapse.dendrite.status_code = "503" + synapse.dendrite.status_message = f"Service at {synapse.axon.ip}:{str(synapse.axon.port)}/{request_name} unavailable." + + except asyncio.TimeoutError as e: + synapse.dendrite.status_code = "408" + synapse.dendrite.status_message = f"Timedout after {timeout} seconds." + + except Exception as e: + synapse.dendrite.status_code = "422" + synapse.dendrite.status_message = ( + f"Failed to parse response object with error: {str(e)}" + ) + + finally: + bittensor.logging.debug( + f"dendrite | <-- | {synapse.get_total_size()} B | {synapse.name} | {synapse.axon.hotkey} | {synapse.axon.ip}:{str(synapse.axon.port)} | {synapse.dendrite.status_code} | {synapse.dendrite.status_message}" + ) + + # Log synapse event history + self.synapse_history.append( + bittensor.Synapse.from_headers(synapse.to_headers()) + ) + + # Return the updated synapse object after deserializing if requested + if deserialize: + return synapse.deserialize() + else: + return synapse + + def preprocess_synapse_for_request( + self, + target_axon_info: bittensor.AxonInfo, + synapse: bittensor.Synapse, + timeout: float = 12.0, + ) -> bittensor.Synapse: + """ + Preprocesses the synapse for making a request. This includes building + headers for Dendrite and Axon and signing the request. + + Args: + target_axon_info (bittensor.AxonInfo): The target axon information. + synapse (bittensor.Synapse): The synapse object to be preprocessed. + timeout (float, optional): The request timeout duration in seconds. + Defaults to 12.0 seconds. + + Returns: + bittensor.Synapse: The preprocessed synapse. + """ + # Set the timeout for the synapse + synapse.timeout = str(timeout) + + # Build the Dendrite headers using the local system's details + synapse.dendrite = bittensor.TerminalInfo( + **{ + "ip": str(self.external_ip), + "version": str(bittensor.__version_as_int__), + "nonce": f"{time.monotonic_ns()}", + "uuid": str(self.uuid), + "hotkey": str(self.keypair.ss58_address), + } + ) + + # Build the Axon headers using the target axon's details + synapse.axon = bittensor.TerminalInfo( + **{ + "ip": str(target_axon_info.ip), + "port": str(target_axon_info.port), + "hotkey": str(target_axon_info.hotkey), + } + ) + + # Sign the request using the dendrite, axon info, and the synapse body hash + message = f"{synapse.dendrite.nonce}.{synapse.dendrite.hotkey}.{synapse.axon.hotkey}.{synapse.dendrite.uuid}.{synapse.body_hash}" + synapse.dendrite.signature = f"0x{self.keypair.sign(message).hex()}" + + return synapse + + def process_server_response( + self, + server_response: Response, + json_response: dict, + local_synapse: bittensor.Synapse, + ): + """ + Processes the server response, updates the local synapse state with the + server's state and merges headers set by the server. + + Args: + server_response (object): The aiohttp response object from the server. + json_response (dict): The parsed JSON response from the server. + local_synapse (bittensor.Synapse): The local synapse object to be updated. + + Raises: + None, but errors in attribute setting are silently ignored. + """ + # Check if the server responded with a successful status code + if server_response.status == 200: + # If the response is successful, overwrite local synapse state with + # server's state only if the protocol allows mutation. To prevent overwrites, + # the protocol must set allow_mutation = False + server_synapse = local_synapse.__class__(**json_response) + for key in local_synapse.dict().keys(): + try: + # Set the attribute in the local synapse from the corresponding + # attribute in the server synapse + setattr(local_synapse, key, getattr(server_synapse, key)) + except: + # Ignore errors during attribute setting + pass + + # Extract server headers and overwrite None values in local synapse headers + server_headers = bittensor.Synapse.from_headers(server_response.headers) + + # Merge dendrite headers + local_synapse.dendrite.__dict__.update( + { + **local_synapse.dendrite.dict(exclude_none=True), + **server_headers.dendrite.dict(exclude_none=True), + } + ) + + # Merge axon headers + local_synapse.axon.__dict__.update( + { + **local_synapse.axon.dict(exclude_none=True), + **server_headers.axon.dict(exclude_none=True), + } + ) + + # Update the status code and status message of the dendrite to match the axon + local_synapse.dendrite.status_code = local_synapse.axon.status_code + local_synapse.dendrite.status_message = local_synapse.axon.status_message + + def __str__(self) -> str: + """ + Returns a string representation of the Dendrite object. + + Returns: + str: The string representation of the Dendrite object in the format "dendrite()". + """ + return "dendrite({})".format(self.keypair.ss58_address) + + def __repr__(self) -> str: + """ + Returns a string representation of the Dendrite object, acting as a fallback for __str__(). + + Returns: + str: The string representation of the Dendrite object in the format "dendrite()". + """ + return self.__str__() diff --git a/bittensor/_subtensor/errors.py b/bittensor/errors.py similarity index 93% rename from bittensor/_subtensor/errors.py rename to bittensor/errors.py index 8f1c25ab83..9053cf6824 100644 --- a/bittensor/_subtensor/errors.py +++ b/bittensor/errors.py @@ -69,3 +69,9 @@ class NotRegisteredError(ChainTransactionError): class NotDelegateError(StakeError): r"""Error raised when a hotkey you are trying to stake to is not a delegate.""" pass + + +class KeyFileError(Exception): + """Error thrown when the keyfile is corrupt, non-writable, non-readable or the password used to decrypt is invalid.""" + + pass diff --git a/bittensor/_subtensor/extrinsics/__init__.py b/bittensor/extrinsics/__init__.py similarity index 100% rename from bittensor/_subtensor/extrinsics/__init__.py rename to bittensor/extrinsics/__init__.py diff --git a/bittensor/_subtensor/extrinsics/delegation.py b/bittensor/extrinsics/delegation.py similarity index 97% rename from bittensor/_subtensor/extrinsics/delegation.py rename to bittensor/extrinsics/delegation.py index 4bac56ade5..aa61f14fb0 100644 --- a/bittensor/_subtensor/extrinsics/delegation.py +++ b/bittensor/extrinsics/delegation.py @@ -29,14 +29,14 @@ def nominate_extrinsic( - subtensor: "bittensor.Subtensor", - wallet: "bittensor.Wallet", + subtensor: "bittensor.subtensor", + wallet: "bittensor.wallet", wait_for_finalization: bool = False, wait_for_inclusion: bool = True, ) -> bool: r"""Becomes a delegate for the hotkey. Args: - wallet ( bittensor.Wallet ): + wallet ( bittensor.wallet ): The wallet to become a delegate for. Returns: success (bool): @@ -96,7 +96,7 @@ def nominate_extrinsic( def delegate_extrinsic( - subtensor: "bittensor.Subtensor", + subtensor: "bittensor.subtensor", wallet: "bittensor.wallet", delegate_ss58: Optional[str] = None, amount: Union[Balance, float] = None, @@ -194,9 +194,6 @@ def delegate_extrinsic( if staking_response == True: # If we successfully staked. # We only wait here if we expect finalization. if not wait_for_finalization and not wait_for_inclusion: - bittensor.__console__.print( - ":white_heavy_check_mark: [green]Sent[/green]" - ) return True bittensor.__console__.print( @@ -245,7 +242,7 @@ def delegate_extrinsic( def undelegate_extrinsic( - subtensor: "bittensor.Subtensor", + subtensor: "bittensor.subtensor", wallet: "bittensor.wallet", delegate_ss58: Optional[str] = None, amount: Union[Balance, float] = None, @@ -339,9 +336,6 @@ def undelegate_extrinsic( if staking_response == True: # If we successfully staked. # We only wait here if we expect finalization. if not wait_for_finalization and not wait_for_inclusion: - bittensor.__console__.print( - ":white_heavy_check_mark: [green]Sent[/green]" - ) return True bittensor.__console__.print( diff --git a/bittensor/_subtensor/extrinsics/log_utilities.py b/bittensor/extrinsics/log_utilities.py similarity index 98% rename from bittensor/_subtensor/extrinsics/log_utilities.py rename to bittensor/extrinsics/log_utilities.py index 88ca1680d1..917aeb1536 100644 --- a/bittensor/_subtensor/extrinsics/log_utilities.py +++ b/bittensor/extrinsics/log_utilities.py @@ -7,7 +7,6 @@ from rich.style import Style from typing import List, Tuple, Callable, Dict, Any, Union, Set import datetime -from prometheus_client import Counter, Gauge, Histogram, Summary, Info import bittensor @@ -322,12 +321,7 @@ def print_response_table( if i == len(sort) - 1: print() - def print_synergy_table( - self, - stats: Dict, - syn_loss_diff: Dict, - sort_col: str, - ): + def print_synergy_table(self, stats: Dict, syn_loss_diff: Dict, sort_col: str): r""" Prints the synergy loss diff matrix with pairwise loss reduction due to synergy (original loss on diagonal). @@ -579,10 +573,7 @@ def print_weights_table( ) def print_console_validator_identifier( - self, - uid: int, - wallet: "bittensor.Wallet", - external_ip: str, + self, uid: int, wallet: "bittensor.wallet", external_ip: str ): r"""Console print for validator identifier.""" @@ -704,7 +695,7 @@ def log_run_info( parameters: torch.nn.parameter.Parameter, uid: int, network: str, - wallet: "bittensor.Wallet", + wallet: "bittensor.wallet", ): r"""Set up prometheus running info.""" @@ -759,10 +750,7 @@ def log_step(self, current_block: int, last_update: int, step_time: int, loss: i self.gauges.labels("loss").set(loss) def log_epoch_end( - self, - uid: int, - metagraph: "bittensor.Metagraph", - current_block: int, + self, uid: int, metagraph: "bittensor.Metagraph", current_block: int ): r"""All prometheus logging at the end of epoch.""" self.gauges.labels("epoch").inc() diff --git a/bittensor/extrinsics/network.py b/bittensor/extrinsics/network.py new file mode 100644 index 0000000000..d2e8ac3455 --- /dev/null +++ b/bittensor/extrinsics/network.py @@ -0,0 +1,202 @@ +# The MIT License (MIT) +# Copyright © 2021 Yuma Rao +# Copyright © 2023 Opentensor Foundation + +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the “Software”), to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of +# the Software. + +# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# 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 json +import time +import bittensor +import bittensor.utils.networking as net +from dataclasses import asdict +from rich.prompt import Confirm + + +def register_subnetwork_extrinsic( + subtensor: "bittensor.subtensor", + wallet: "bittensor.wallet", + wait_for_inclusion: bool = False, + wait_for_finalization: bool = True, + prompt: bool = False, +) -> bool: + r"""Registers a new subnetwork + Args: + wallet (bittensor.wallet): + bittensor wallet object. + wait_for_inclusion (bool): + If set, waits for the extrinsic to enter a block before returning true, + or returns false if the extrinsic fails to enter the block within the timeout. + wait_for_finalization (bool): + If set, waits for the extrinsic to be finalized on the chain before returning true, + or returns false if the extrinsic fails to be finalized within the timeout. + prompt (bool): + If true, the call waits for confirmation from the user before proceeding. + Returns: + success (bool): + flag is true if extrinsic was finalized or included in the block. + If we did not wait for finalization / inclusion, the response is true. + """ + your_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address) + burn_cost = bittensor.utils.balance.Balance(subtensor.get_subnet_burn_cost()) + if burn_cost > your_balance: + bittensor.__console__.print( + f"Your balance of: [green]{your_balance}[/green] is not enough to pay the subnet burn cost of: [green]{burn_cost}[/green]" + ) + return False + + if prompt: + bittensor.__console__.print(f"Your balance is: [green]{your_balance}[/green]") + if not Confirm.ask( + f"Do you want to register a subnet for [green]{ burn_cost }[/green]?" + ): + return False + + wallet.coldkey # unlock coldkey + + with bittensor.__console__.status(":satellite: Registering subnet..."): + with subtensor.substrate as substrate: + # create extrinsic call + call = substrate.compose_call( + call_module="SubtensorModule", + call_function="register_network", + call_params={"immunity_period": 0, "reg_allowed": True}, + ) + extrinsic = substrate.create_signed_extrinsic( + call=call, keypair=wallet.coldkey + ) + response = substrate.submit_extrinsic( + extrinsic, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + + # We only wait here if we expect finalization. + if not wait_for_finalization and not wait_for_inclusion: + return True + + # process if registration successful + response.process_events() + if not response.is_success: + bittensor.__console__.print( + ":cross_mark: [red]Failed[/red]: error:{}".format( + response.error_message + ) + ) + time.sleep(0.5) + + # Successful registration, final check for membership + else: + bittensor.__console__.print( + f":white_heavy_check_mark: [green]Registered subnetwork with netuid: {response.triggered_events[1].value['event']['attributes'][0]}[/green]" + ) + return True + + +from ..commands.network import HYPERPARAMS + + +def set_hyperparameter_extrinsic( + subtensor: "bittensor.subtensor", + wallet: "bittensor.wallet", + netuid: int, + parameter: str, + value, + wait_for_inclusion: bool = False, + wait_for_finalization: bool = True, + prompt: bool = False, +) -> bool: + r"""Sets a hyperparameter for a specific subnetwork. + Args: + wallet (bittensor.wallet): + bittensor wallet object. + netuid (int): + Subnetwork uid. + parameter (str): + Hyperparameter name. + value (any): + New hyperparameter value. + wait_for_inclusion (bool): + If set, waits for the extrinsic to enter a block before returning true, + or returns false if the extrinsic fails to enter the block within the timeout. + wait_for_finalization (bool): + If set, waits for the extrinsic to be finalized on the chain before returning true, + or returns false if the extrinsic fails to be finalized within the timeout. + prompt (bool): + If true, the call waits for confirmation from the user before proceeding. + Returns: + success (bool): + flag is true if extrinsic was finalized or included in the block. + If we did not wait for finalization / inclusion, the response is true. + """ + if subtensor.get_subnet_owner(netuid) != wallet.coldkeypub.ss58_address: + bittensor.__console__.print( + ":cross_mark: [red]This wallet doesn't own the specified subnet.[/red]" + ) + return False + + wallet.coldkey # unlock coldkey + + extrinsic = HYPERPARAMS.get(parameter) + if extrinsic == None: + bittensor.__console__.print( + ":cross_mark: [red]Invalid hyperparameter specified.[/red]" + ) + return False + + with bittensor.__console__.status( + f":satellite: Setting hyperparameter {parameter} to {value} on subnet: {netuid} ..." + ): + with subtensor.substrate as substrate: + extrinsic_params = substrate.get_metadata_call_function( + "SubtensorModule", extrinsic + ) + value_argument = extrinsic_params["fields"][ + len(extrinsic_params["fields"]) - 1 + ] + + # create extrinsic call + call = substrate.compose_call( + call_module="SubtensorModule", + call_function=extrinsic, + call_params={"netuid": netuid, str(value_argument["name"]): value}, + ) + extrinsic = substrate.create_signed_extrinsic( + call=call, keypair=wallet.coldkey + ) + response = substrate.submit_extrinsic( + extrinsic, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + + # We only wait here if we expect finalization. + if not wait_for_finalization and not wait_for_inclusion: + return True + + # process if registration successful + response.process_events() + if not response.is_success: + bittensor.__console__.print( + ":cross_mark: [red]Failed[/red]: error:{}".format( + response.error_message + ) + ) + time.sleep(0.5) + + # Successful registration, final check for membership + else: + bittensor.__console__.print( + f":white_heavy_check_mark: [green]Hyper parameter {parameter} changed to {value}[/green]" + ) + return True diff --git a/bittensor/_subtensor/extrinsics/prometheus.py b/bittensor/extrinsics/prometheus.py similarity index 98% rename from bittensor/_subtensor/extrinsics/prometheus.py rename to bittensor/extrinsics/prometheus.py index 44fe621858..55a3224852 100644 --- a/bittensor/_subtensor/extrinsics/prometheus.py +++ b/bittensor/extrinsics/prometheus.py @@ -20,12 +20,10 @@ import json from rich.prompt import Confirm import bittensor.utils.networking as net -from ..errors import * -from ..types import PrometheusServeCallParams def prometheus_extrinsic( - subtensor: "bittensor.Subtensor", + subtensor: "bittensor.subtensor", wallet: "bittensor.wallet", port: int, netuid: int, diff --git a/bittensor/_subtensor/extrinsics/registration.py b/bittensor/extrinsics/registration.py similarity index 68% rename from bittensor/_subtensor/extrinsics/registration.py rename to bittensor/extrinsics/registration.py index 2dc0aa3958..9799234363 100644 --- a/bittensor/_subtensor/extrinsics/registration.py +++ b/bittensor/extrinsics/registration.py @@ -21,15 +21,13 @@ import torch import time from rich.prompt import Confirm -from typing import List, Dict, Union, Optional, Tuple -import bittensor.utils.networking as net +from typing import List, Union, Optional, Tuple from bittensor.utils.registration import POWSolution, create_pow -from ..errors import * def register_extrinsic( - subtensor: "bittensor.Subtensor", - wallet: "bittensor.Wallet", + subtensor: "bittensor.subtensor", + wallet: "bittensor.wallet", netuid: int, wait_for_inclusion: bool = False, wait_for_finalization: bool = True, @@ -91,14 +89,8 @@ def register_extrinsic( wallet.hotkey.ss58_address, netuid=netuid ) if not neuron.is_null: - bittensor.__console__.print( - ":white_heavy_check_mark: [green]Already Registered[/green]:\n" - "uid: [bold white]{}[/bold white]\n" - "netuid: [bold white]{}[/bold white]\n" - "hotkey: [bold white]{}[/bold white]\n" - "coldkey: [bold white]{}[/bold white]".format( - neuron.uid, neuron.netuid, neuron.hotkey, neuron.coldkey - ) + bittensor.logging.debug( + f"Wallet {wallet} is already registered on {neuron.netuid} with {neuron.uid}" ) return True @@ -129,9 +121,9 @@ def register_extrinsic( wallet, netuid, output_in_place, - cuda, - dev_id, - TPB, + cuda=cuda, + dev_id=dev_id, + TPB=TPB, num_processes=num_processes, update_interval=update_interval, log_verbose=log_verbose, @@ -142,6 +134,7 @@ def register_extrinsic( wallet, netuid, output_in_place, + cuda=cuda, num_processes=num_processes, update_interval=update_interval, log_verbose=log_verbose, @@ -151,8 +144,7 @@ def register_extrinsic( if not pow_result: # might be registered already on this subnet is_registered = subtensor.is_hotkey_registered( - netuid=netuid, - hotkey_ss58=wallet.hotkey.ss58_address, + netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address ) if is_registered: bittensor.__console__.print( @@ -191,8 +183,7 @@ def register_extrinsic( else: bittensor.__console__.print(":satellite: Checking Balance...") is_registered = subtensor.is_hotkey_registered( - netuid=netuid, - hotkey_ss58=wallet.hotkey.ss58_address, + netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address ) if is_registered: bittensor.__console__.print( @@ -226,8 +217,8 @@ def register_extrinsic( def burned_register_extrinsic( - subtensor: "bittensor.Subtensor", - wallet: "bittensor.Wallet", + subtensor: "bittensor.subtensor", + wallet: "bittensor.wallet", netuid: int, wait_for_inclusion: bool = False, wait_for_finalization: bool = True, @@ -316,8 +307,7 @@ def burned_register_extrinsic( ) ) is_registered = subtensor.is_hotkey_registered( - netuid=netuid, - hotkey_ss58=wallet.hotkey.ss58_address, + netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address ) if is_registered: bittensor.__console__.print( @@ -329,3 +319,136 @@ def burned_register_extrinsic( bittensor.__console__.print( ":cross_mark: [red]Unknown error. Neuron not found.[/red]" ) + + +def run_faucet_extrinsic( + subtensor: "bittensor.subtensor", + wallet: "bittensor.wallet", + wait_for_inclusion: bool = False, + wait_for_finalization: bool = True, + prompt: bool = False, + max_allowed_attempts: int = 3, + output_in_place: bool = True, + cuda: bool = False, + dev_id: Union[List[int], int] = 0, + TPB: int = 256, + num_processes: Optional[int] = None, + update_interval: Optional[int] = None, + log_verbose: bool = False, +) -> bool: + r"""Runs a continual POW to get a faucet of TAO on the test net. + Args: + wallet (bittensor.wallet): + bittensor wallet object. + prompt (bool): + If true, the call waits for confirmation from the user before proceeding. + wait_for_inclusion (bool): + If set, waits for the extrinsic to enter a block before returning true, + or returns false if the extrinsic fails to enter the block within the timeout. + wait_for_finalization (bool): + If set, waits for the extrinsic to be finalized on the chain before returning true, + or returns false if the extrinsic fails to be finalized within the timeout. + max_allowed_attempts (int): + Maximum number of attempts to register the wallet. + cuda (bool): + If true, the wallet should be registered using CUDA device(s). + dev_id (Union[List[int], int]): + The CUDA device id to use, or a list of device ids. + TPB (int): + The number of threads per block (CUDA). + num_processes (int): + The number of processes to use to register. + update_interval (int): + The number of nonces to solve between updates. + log_verbose (bool): + If true, the registration process will log more information. + Returns: + success (bool): + flag is true if extrinsic was finalized or uncluded in the block. + If we did not wait for finalization / inclusion, the response is true. + """ + if prompt: + if not Confirm.ask( + "Run Faucet ?\n coldkey: [bold white]{}[/bold white]\n network: [bold white]{}[/bold white]".format( + wallet.coldkeypub.ss58_address, + subtensor.network, + ) + ): + return False + + # Unlock coldkey + wallet.coldkey + + # Get previous balance. + old_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address) + + # Attempt rolling registration. + attempts = 1 + while True: + try: + pow_result = None + while pow_result == None or pow_result.is_stale(subtensor=subtensor): + # Solve latest POW. + if cuda: + if not torch.cuda.is_available(): + if prompt: + bittensor.__console__.error("CUDA is not available.") + return False + pow_result: Optional[POWSolution] = create_pow( + subtensor, + wallet, + -1, + output_in_place, + cuda=cuda, + dev_id=dev_id, + TPB=TPB, + num_processes=num_processes, + update_interval=update_interval, + log_verbose=log_verbose, + ) + else: + pow_result: Optional[POWSolution] = create_pow( + subtensor, + wallet, + -1, + output_in_place, + cuda=cuda, + num_processes=num_processes, + update_interval=update_interval, + log_verbose=log_verbose, + ) + call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="faucet", + call_params={ + "block_number": pow_result.block_number, + "nonce": pow_result.nonce, + "work": [int(byte_) for byte_ in pow_result.seal], + }, + ) + extrinsic = subtensor.substrate.create_signed_extrinsic( + call=call, keypair=wallet.coldkey + ) + response = subtensor.substrate.submit_extrinsic( + extrinsic, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + + # process if registration successful, try again if pow is still valid + response.process_events() + if not response.is_success: + bittensor.__console__.print( + f":cross_mark: [red]Failed[/red]: Error: {response.error_message}" + ) + + # Successful registration + else: + new_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address) + bittensor.__console__.print( + f"Balance: [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" + ) + old_balance = new_balance + + except KeyboardInterrupt: + return True, "Done" diff --git a/bittensor/extrinsics/root.py b/bittensor/extrinsics/root.py new file mode 100644 index 0000000000..136612ba6f --- /dev/null +++ b/bittensor/extrinsics/root.py @@ -0,0 +1,225 @@ +# The MIT License (MIT) +# Copyright © 2021 Yuma Rao +# Copyright © 2023 Opentensor Foundation + +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the “Software”), to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of +# the Software. + +# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# 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 bittensor + +import time +import torch +from rich.prompt import Confirm +from typing import Union, List +import bittensor.utils.weight_utils as weight_utils + +from loguru import logger + +logger = logger.opt(colors=True) + + +def root_register_extrinsic( + subtensor: "bittensor.subtensor", + wallet: "bittensor.wallet", + wait_for_inclusion: bool = False, + wait_for_finalization: bool = True, + prompt: bool = False, +) -> bool: + r"""Registers the wallet to root network. + Args: + wallet (bittensor.wallet): + bittensor wallet object. + wait_for_inclusion (bool): + If set, waits for the extrinsic to enter a block before returning true, + or returns false if the extrinsic fails to enter the block within the timeout. + wait_for_finalization (bool): + If set, waits for the extrinsic to be finalized on the chain before returning true, + or returns false if the extrinsic fails to be finalized within the timeout. + prompt (bool): + If true, the call waits for confirmation from the user before proceeding. + Returns: + success (bool): + flag is true if extrinsic was finalized or uncluded in the block. + If we did not wait for finalization / inclusion, the response is true. + """ + + wallet.coldkey # unlock coldkey + + is_registered = subtensor.is_hotkey_registered( + netuid=0, hotkey_ss58=wallet.hotkey.ss58_address + ) + if is_registered: + bittensor.__console__.print( + f":white_heavy_check_mark: [green]Already registered on root network.[/green]" + ) + return True + + if prompt: + # Prompt user for confirmation. + if not Confirm.ask(f"Register to root network?"): + return False + + with bittensor.__console__.status(":satellite: Registering to root network..."): + success, err_msg = subtensor._do_root_register( + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + + if success != True or success == False: + bittensor.__console__.print( + ":cross_mark: [red]Failed[/red]: error:{}".format(err_msg) + ) + time.sleep(0.5) + + # Successful registration, final check for neuron and pubkey + else: + is_registered = subtensor.is_hotkey_registered( + netuid=0, hotkey_ss58=wallet.hotkey.ss58_address + ) + if is_registered: + bittensor.__console__.print( + ":white_heavy_check_mark: [green]Registered[/green]" + ) + return True + else: + # neuron not found, try again + bittensor.__console__.print( + ":cross_mark: [red]Unknown error. Neuron not found.[/red]" + ) + + +def set_root_weights_extrinsic( + subtensor: "bittensor.subtensor", + wallet: "bittensor.wallet", + netuids: Union[torch.LongTensor, list], + weights: Union[torch.FloatTensor, list], + version_key: int = 0, + wait_for_inclusion: bool = False, + wait_for_finalization: bool = False, + prompt: bool = False, +) -> bool: + r"""Sets the given weights and values on chain for wallet hotkey account. + Args: + wallet (bittensor.wallet): + bittensor wallet object. + netuids (List[int]): + netuid of the subent to set weights for. + weights ( Union[torch.FloatTensor, list]): + weights to set which must floats and correspond to the passed uids. + version_key (int): + version key of the validator. + wait_for_inclusion (bool): + if set, waits for the extrinsic to enter a block before returning true, + or returns false if the extrinsic fails to enter the block within the timeout. + wait_for_finalization (bool): + if set, waits for the extrinsic to be finalized on the chain before returning true, + or returns false if the extrinsic fails to be finalized within the timeout. + prompt (bool): + If true, the call waits for confirmation from the user before proceeding. + Returns: + success (bool): + flag is true if extrinsic was finalized or uncluded in the block. + If we did not wait for finalization / inclusion, the response is true. + """ + # First convert types. + if isinstance(netuids, list): + netuids = torch.tensor(netuids, dtype=torch.int64) + if isinstance(weights, list): + weights = torch.tensor(weights, dtype=torch.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_uids = netuids[non_zero_weight_idx] + non_zero_weights = weights[non_zero_weight_idx] + if non_zero_weights.numel() < min_allowed_weights: + raise ValueError( + "The minimum number of weights required to set weights is {}, got {}".format( + min_allowed_weights, non_zero_weights.numel() + ) + ) + + # Normalize the weights to max value. + formatted_weights = bittensor.utils.weight_utils.normalize_max_weight( + x=weights, limit=max_weight_limit + ) + bittensor.__console__.print( + f"\nNormalized weights: \n\t{weights} -> {formatted_weights}\n" + ) + + # Ask before moving on. + if prompt: + if not Confirm.ask( + "Do you want to set the following root weights?:\n[bold white] weights: {}\n uids: {}[/bold white ]?".format( + formatted_weights, netuids + ) + ): + return False + + with bittensor.__console__.status( + ":satellite: Setting root weights on [white]{}[/white] ...".format( + subtensor.network + ) + ): + try: + weight_uids, weight_vals = weight_utils.convert_weights_and_uids_for_emit( + netuids, weights + ) + success, error_message = subtensor._do_set_weights( + wallet=wallet, + netuid=0, + uids=weight_uids, + vals=weight_vals, + version_key=version_key, + wait_for_finalization=wait_for_finalization, + wait_for_inclusion=wait_for_inclusion, + ) + + bittensor.__console__.print(success, error_message) + + if not wait_for_finalization and not wait_for_inclusion: + return True + + if success == True: + bittensor.__console__.print( + ":white_heavy_check_mark: [green]Finalized[/green]" + ) + bittensor.logging.success( + prefix="Set weights", + sufix="Finalized: " + str(success), + ) + return True + else: + bittensor.__console__.print( + ":cross_mark: [red]Failed[/red]: error:{}".format(error_message) + ) + bittensor.logging.warning( + prefix="Set weights", + sufix="Failed: " + str(error_message), + ) + return False + + except Exception as e: + # TODO( devs ): lets remove all of the bittensor.__console__ calls and replace with loguru. + bittensor.__console__.print( + ":cross_mark: [red]Failed[/red]: error:{}".format(e) + ) + bittensor.logging.warning( + prefix="Set weights", sufix="Failed: " + str(e) + ) + return False diff --git a/bittensor/_subtensor/extrinsics/senate.py b/bittensor/extrinsics/senate.py similarity index 94% rename from bittensor/_subtensor/extrinsics/senate.py rename to bittensor/extrinsics/senate.py index 86faacca6f..ee27a20f15 100644 --- a/bittensor/_subtensor/extrinsics/senate.py +++ b/bittensor/extrinsics/senate.py @@ -25,8 +25,8 @@ def register_senate_extrinsic( - subtensor: "bittensor.Subtensor", - wallet: "bittensor.Wallet", + subtensor: "bittensor.subtensor", + wallet: "bittensor.wallet", wait_for_inclusion: bool = False, wait_for_finalization: bool = True, prompt: bool = False, @@ -75,9 +75,6 @@ def register_senate_extrinsic( # We only wait here if we expect finalization. if not wait_for_finalization and not wait_for_inclusion: - bittensor.__console__.print( - ":white_heavy_check_mark: [green]Sent[/green]" - ) return True # process if registration successful @@ -107,8 +104,8 @@ def register_senate_extrinsic( def leave_senate_extrinsic( - subtensor: "bittensor.Subtensor", - wallet: "bittensor.Wallet", + subtensor: "bittensor.subtensor", + wallet: "bittensor.wallet", wait_for_inclusion: bool = False, wait_for_finalization: bool = True, prompt: bool = False, @@ -157,9 +154,6 @@ def leave_senate_extrinsic( # We only wait here if we expect finalization. if not wait_for_finalization and not wait_for_inclusion: - bittensor.__console__.print( - ":white_heavy_check_mark: [green]Sent[/green]" - ) return True # process if registration successful @@ -189,8 +183,8 @@ def leave_senate_extrinsic( def vote_senate_extrinsic( - subtensor: "bittensor.Subtensor", - wallet: "bittensor.Wallet", + subtensor: "bittensor.subtensor", + wallet: "bittensor.wallet", proposal_hash: str, proposal_idx: int, vote: bool, @@ -247,9 +241,6 @@ def vote_senate_extrinsic( # We only wait here if we expect finalization. if not wait_for_finalization and not wait_for_inclusion: - bittensor.__console__.print( - ":white_heavy_check_mark: [green]Sent[/green]" - ) return True # process if vote successful diff --git a/bittensor/_subtensor/extrinsics/serving.py b/bittensor/extrinsics/serving.py similarity index 67% rename from bittensor/_subtensor/extrinsics/serving.py rename to bittensor/extrinsics/serving.py index 24997e1311..4bdfde7f76 100644 --- a/bittensor/_subtensor/extrinsics/serving.py +++ b/bittensor/extrinsics/serving.py @@ -15,17 +15,15 @@ # 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 bittensor - import json -from rich.prompt import Confirm +import bittensor +from dataclasses import asdict import bittensor.utils.networking as net -from ..errors import * -from ..types import AxonServeCallParams +from rich.prompt import Confirm def serve_extrinsic( - subtensor: "bittensor.Subtensor", + subtensor: "bittensor.subtensor", wallet: "bittensor.wallet", ip: str, port: int, @@ -74,43 +72,35 @@ def serve_extrinsic( "port": port, "ip_type": net.ip_version(ip), "netuid": netuid, + "hotkey": wallet.hotkey.ss58_address, "coldkey": wallet.coldkeypub.ss58_address, "protocol": protocol, "placeholder1": placeholder1, "placeholder2": placeholder2, } - with bittensor.__console__.status(":satellite: Checking Axon..."): - neuron = subtensor.get_neuron_for_pubkey_and_subnet( - wallet.hotkey.ss58_address, netuid=netuid - ) - neuron_up_to_date = not neuron.is_null and params == { - "version": neuron.axon_info.version, - "ip": net.ip_to_int(neuron.axon_info.ip), - "port": neuron.axon_info.port, - "ip_type": neuron.axon_info.ip_type, - "netuid": neuron.netuid, - "coldkey": neuron.coldkey, - "protocol": neuron.axon_info.protocol, - "placeholder1": neuron.axon_info.placeholder1, - "placeholder2": neuron.axon_info.placeholder2, - } + bittensor.logging.debug("Checking axon ...") + neuron = subtensor.get_neuron_for_pubkey_and_subnet( + wallet.hotkey.ss58_address, netuid=netuid + ) + neuron_up_to_date = not neuron.is_null and params == { + "version": neuron.axon_info.version, + "ip": net.ip_to_int(neuron.axon_info.ip), + "port": neuron.axon_info.port, + "ip_type": neuron.axon_info.ip_type, + "netuid": neuron.netuid, + "hotkey": neuron.hotkey, + "coldkey": neuron.coldkey, + "protocol": neuron.axon_info.protocol, + "placeholder1": neuron.axon_info.placeholder1, + "placeholder2": neuron.axon_info.placeholder2, + } output = params.copy() output["coldkey"] = wallet.coldkeypub.ss58_address output["hotkey"] = wallet.hotkey.ss58_address if neuron_up_to_date: - bittensor.__console__.print( - f":white_heavy_check_mark: [green]Axon already Served[/green]\n" - f"[green not bold]- coldkey: [/green not bold][white not bold]{output['coldkey']}[/white not bold] \n" - f"[green not bold]- hotkey: [/green not bold][white not bold]{output['hotkey']}[/white not bold] \n" - f"[green not bold]- Status: [/green not bold] |" - f"[green not bold] ip: [/green not bold][white not bold]{net.int_to_ip(output['ip'])}[/white not bold] |" - f"[green not bold] ip_type: [/green not bold][white not bold]{output['ip_type']}[/white not bold] |" - f"[green not bold] port: [/green not bold][white not bold]{output['port']}[/white not bold] | " - f"[green not bold] netuid: [/green not bold][white not bold]{output['netuid']}[/white not bold] |" - f"[green not bold] protocol: [/green not bold][white not bold]{output['protocol']}[/white not bold] |" - f"[green not bold] version: [/green not bold][white not bold]{output['version']}[/white not bold] |" + bittensor.logging.debug( + f"Axon already served on: AxonInfo({wallet.hotkey.ss58_address},{ip}:{port}) " ) - return True if prompt: @@ -124,39 +114,33 @@ def serve_extrinsic( ): return False - with bittensor.__console__.status( - ":satellite: Serving axon on: [white]{}:{}[/white] ...".format( - subtensor.network, netuid - ) - ): - success, error_message = subtensor._do_serve_axon( - wallet=wallet, - call_params=params, - wait_for_finalization=wait_for_finalization, - wait_for_inclusion=wait_for_inclusion, - ) + bittensor.logging.debug( + f"Serving axon with: AxonInfo({wallet.hotkey.ss58_address},{ip}:{port}) -> {subtensor.network}:{netuid}" + ) + success, error_message = subtensor._do_serve_axon( + wallet=wallet, + call_params=params, + wait_for_finalization=wait_for_finalization, + wait_for_inclusion=wait_for_inclusion, + ) - if wait_for_inclusion or wait_for_finalization: - if success == True: - bittensor.__console__.print( - ":white_heavy_check_mark: [green]Served[/green]\n [bold white]{}[/bold white]".format( - json.dumps(params, indent=4, sort_keys=True) - ) - ) - return True - else: - bittensor.__console__.print( - ":cross_mark: [green]Failed to Serve axon[/green] error: {}".format( - error_message - ) - ) - return False - else: + if wait_for_inclusion or wait_for_finalization: + if success == True: + bittensor.logging.debug( + f"Axon served with: AxonInfo({wallet.hotkey.ss58_address},{ip}:{port}) on {subtensor.network}:{netuid} " + ) return True + else: + bittensor.logging.debug( + f"Axon failed to served with error: {error_message} " + ) + return False + else: + return True def serve_axon_extrinsic( - subtensor: "bittensor.Subtensor", + subtensor: "bittensor.subtensor", netuid: int, axon: "bittensor.Axon", wait_for_inclusion: bool = False, diff --git a/bittensor/_subtensor/extrinsics/set_weights.py b/bittensor/extrinsics/set_weights.py similarity index 86% rename from bittensor/_subtensor/extrinsics/set_weights.py rename to bittensor/extrinsics/set_weights.py index e6bc3dc0a8..bd5b623bd9 100644 --- a/bittensor/_subtensor/extrinsics/set_weights.py +++ b/bittensor/extrinsics/set_weights.py @@ -22,7 +22,6 @@ from rich.prompt import Confirm from typing import Union import bittensor.utils.weight_utils as weight_utils -from ..errors import * from loguru import logger @@ -30,7 +29,7 @@ def set_weights_extrinsic( - subtensor: "bittensor.Subtensor", + subtensor: "bittensor.subtensor", wallet: "bittensor.wallet", netuid: int, uids: Union[torch.LongTensor, list], @@ -100,9 +99,6 @@ def set_weights_extrinsic( ) if not wait_for_finalization and not wait_for_inclusion: - bittensor.__console__.print( - ":white_heavy_check_mark: [green]Sent[/green]" - ) return True if success == True: @@ -133,24 +129,3 @@ def set_weights_extrinsic( prefix="Set weights", sufix="Failed: " + str(e) ) return False - - # TODO( devs ): this code is dead. - if response.is_success: - bittensor.__console__.print( - "Set weights:\n[bold white] weights: {}\n uids: {}[/bold white ]".format( - [float(v / 4294967295) for v in weight_vals], weight_uids - ) - ) - message = ( - "Success: " - + f"Set {len(uids)} weights, top 5 weights" - + str( - list( - zip(uids.tolist()[:5], [round(w, 4) for w in weights.tolist()[:5]]) - ) - ) - ) - logger.debug("Set weights:".ljust(20) + message) - return True - - return False diff --git a/bittensor/_subtensor/extrinsics/staking.py b/bittensor/extrinsics/staking.py similarity index 95% rename from bittensor/_subtensor/extrinsics/staking.py rename to bittensor/extrinsics/staking.py index a5a43c3f0c..926717270c 100644 --- a/bittensor/_subtensor/extrinsics/staking.py +++ b/bittensor/extrinsics/staking.py @@ -17,16 +17,14 @@ # DEALINGS IN THE SOFTWARE. import bittensor - from rich.prompt import Confirm from time import sleep from typing import List, Dict, Union, Optional from bittensor.utils.balance import Balance -from ..errors import * def add_stake_extrinsic( - subtensor: "bittensor.Subtensor", + subtensor: "bittensor.subtensor", wallet: "bittensor.wallet", hotkey_ss58: Optional[str] = None, amount: Union[Balance, float] = None, @@ -57,9 +55,9 @@ def add_stake_extrinsic( If we did not wait for finalization / inclusion, the response is true. Raises: - NotRegisteredError: + bittensor.errors.NotRegisteredError: If the wallet is not registered on the chain. - NotDelegateError: + bittensor.errors.NotDelegateError: If the hotkey is not a delegate on the chain. """ # Decrypt keys, @@ -84,7 +82,7 @@ def add_stake_extrinsic( if not own_hotkey: # This is not the wallet's own hotkey so we are delegating. if not subtensor.is_hotkey_delegate(hotkey_ss58): - raise NotDelegateError( + raise bittensor.errors.NotDelegateError( "Hotkey: {} is not a delegate.".format(hotkey_ss58) ) @@ -156,9 +154,6 @@ def add_stake_extrinsic( if staking_response == True: # If we successfully staked. # We only wait here if we expect finalization. if not wait_for_finalization and not wait_for_inclusion: - bittensor.__console__.print( - ":white_heavy_check_mark: [green]Sent[/green]" - ) return True bittensor.__console__.print( @@ -196,20 +191,20 @@ def add_stake_extrinsic( ) return False - except NotRegisteredError as e: + except bittensor.errors.NotRegisteredError as e: bittensor.__console__.print( ":cross_mark: [red]Hotkey: {} is not registered.[/red]".format( wallet.hotkey_str ) ) return False - except StakeError as e: + except bittensor.errors.StakeError as e: bittensor.__console__.print(":cross_mark: [red]Stake Error: {}[/red]".format(e)) return False def add_stake_multiple_extrinsic( - subtensor: "bittensor.Subtensor", + subtensor: "bittensor.subtensor", wallet: "bittensor.wallet", hotkey_ss58s: List[str], amounts: List[Union[Balance, float]] = None, @@ -368,9 +363,6 @@ def add_stake_multiple_extrinsic( sleep(tx_rate_limit_blocks * 12) # 12 seconds per block if not wait_for_finalization and not wait_for_inclusion: - bittensor.__console__.print( - ":white_heavy_check_mark: [green]Sent[/green]" - ) old_balance -= staking_balance successful_stakes += 1 if staking_all: @@ -409,14 +401,14 @@ def add_stake_multiple_extrinsic( ) continue - except NotRegisteredError as e: + except bittensor.errors.NotRegisteredError as e: bittensor.__console__.print( ":cross_mark: [red]Hotkey: {} is not registered.[/red]".format( hotkey_ss58 ) ) continue - except StakeError as e: + except bittensor.errors.StakeError as e: bittensor.__console__.print( ":cross_mark: [red]Stake Error: {}[/red]".format(e) ) @@ -440,7 +432,7 @@ def add_stake_multiple_extrinsic( def __do_add_stake_single( - subtensor: "bittensor.Subtensor", + subtensor: "bittensor.subtensor", wallet: "bittensor.wallet", hotkey_ss58: str, amount: "bittensor.Balance", @@ -469,11 +461,11 @@ def __do_add_stake_single( flag is true if extrinsic was finalized or uncluded in the block. If we did not wait for finalization / inclusion, the response is true. Raises: - StakeError: + bittensor.errors.StakeError: If the extrinsic fails to be finalized or included in the block. - NotDelegateError: + bittensor.errors.NotDelegateError: If the hotkey is not a delegate. - NotRegisteredError: + bittensor.errors.NotRegisteredError: If the hotkey is not registered in any subnets. """ @@ -486,7 +478,9 @@ def __do_add_stake_single( # We are delegating. # Verify that the hotkey is a delegate. if not subtensor.is_hotkey_delegate(hotkey_ss58=hotkey_ss58): - raise NotDelegateError("Hotkey: {} is not a delegate.".format(hotkey_ss58)) + raise bittensor.errors.NotDelegateError( + "Hotkey: {} is not a delegate.".format(hotkey_ss58) + ) success = subtensor._do_stake( wallet=wallet, diff --git a/bittensor/_subtensor/extrinsics/transfer.py b/bittensor/extrinsics/transfer.py similarity index 97% rename from bittensor/_subtensor/extrinsics/transfer.py rename to bittensor/extrinsics/transfer.py index b1606f7d85..86fcce70a5 100644 --- a/bittensor/_subtensor/extrinsics/transfer.py +++ b/bittensor/extrinsics/transfer.py @@ -20,13 +20,12 @@ from rich.prompt import Confirm from typing import List, Dict, Union -from bittensor.utils.balance import Balance -from bittensor.utils import is_valid_bittensor_address_or_public_key -from ..errors import * +from ..utils.balance import Balance +from ..utils import is_valid_bittensor_address_or_public_key def transfer_extrinsic( - subtensor: "bittensor.Subtensor", + subtensor: "bittensor.subtensor", wallet: "bittensor.wallet", dest: str, amount: Union[Balance, float], diff --git a/bittensor/_subtensor/extrinsics/unstaking.py b/bittensor/extrinsics/unstaking.py similarity index 96% rename from bittensor/_subtensor/extrinsics/unstaking.py rename to bittensor/extrinsics/unstaking.py index 9e1118a3f8..a4a8e3c665 100644 --- a/bittensor/_subtensor/extrinsics/unstaking.py +++ b/bittensor/extrinsics/unstaking.py @@ -17,16 +17,14 @@ # DEALINGS IN THE SOFTWARE. import bittensor - from rich.prompt import Confirm from time import sleep from typing import List, Dict, Union, Optional from bittensor.utils.balance import Balance -from ..errors import * def __do_remove_stake_single( - subtensor: "bittensor.Subtensor", + subtensor: "bittensor.subtensor", wallet: "bittensor.wallet", hotkey_ss58: str, amount: "bittensor.Balance", @@ -55,9 +53,9 @@ def __do_remove_stake_single( flag is true if extrinsic was finalized or uncluded in the block. If we did not wait for finalization / inclusion, the response is true. Raises: - StakeError: + bittensor.errors.StakeError: If the extrinsic fails to be finalized or included in the block. - NotRegisteredError: + bittensor.errors.NotRegisteredError: If the hotkey is not registered in any subnets. """ @@ -76,7 +74,7 @@ def __do_remove_stake_single( def unstake_extrinsic( - subtensor: "bittensor.Subtensor", + subtensor: "bittensor.subtensor", wallet: "bittensor.wallet", hotkey_ss58: Optional[str] = None, amount: Union[Balance, float] = None, @@ -168,9 +166,6 @@ def unstake_extrinsic( if staking_response == True: # If we successfully unstaked. # We only wait here if we expect finalization. if not wait_for_finalization and not wait_for_inclusion: - bittensor.__console__.print( - ":white_heavy_check_mark: [green]Sent[/green]" - ) return True bittensor.__console__.print( @@ -204,20 +199,20 @@ def unstake_extrinsic( ) return False - except NotRegisteredError as e: + except bittensor.errors.NotRegisteredError as e: bittensor.__console__.print( ":cross_mark: [red]Hotkey: {} is not registered.[/red]".format( wallet.hotkey_str ) ) return False - except StakeError as e: + except bittensor.errors.StakeError as e: bittensor.__console__.print(":cross_mark: [red]Stake Error: {}[/red]".format(e)) return False def unstake_multiple_extrinsic( - subtensor: "bittensor.Subtensor", + subtensor: "bittensor.subtensor", wallet: "bittensor.wallet", hotkey_ss58s: List[str], amounts: List[Union[Balance, float]] = None, @@ -357,9 +352,6 @@ def unstake_multiple_extrinsic( sleep(tx_rate_limit_blocks * 12) # 12 seconds per block if not wait_for_finalization and not wait_for_inclusion: - bittensor.__console__.print( - ":white_heavy_check_mark: [green]Sent[/green]" - ) successful_unstakes += 1 continue @@ -389,12 +381,12 @@ def unstake_multiple_extrinsic( ) continue - except NotRegisteredError as e: + except bittensor.errors.NotRegisteredError as e: bittensor.__console__.print( ":cross_mark: [red]{} is not registered.[/red]".format(hotkey_ss58) ) continue - except StakeError as e: + except bittensor.errors.StakeError as e: bittensor.__console__.print( ":cross_mark: [red]Stake Error: {}[/red]".format(e) ) diff --git a/bittensor/keyfile.py b/bittensor/keyfile.py new file mode 100644 index 0000000000..935d47116a --- /dev/null +++ b/bittensor/keyfile.py @@ -0,0 +1,831 @@ +# The MIT License (MIT) +# Copyright © 2021 Yuma Rao + +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the “Software”), to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of +# the Software. + +# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# 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 os +import base64 +import json +import stat +import getpass +import bittensor +from bittensor.errors import KeyFileError +from typing import Optional +from pathlib import Path + +from ansible_vault import Vault +from ansible.parsing.vault import AnsibleVaultError +from cryptography.exceptions import InvalidSignature, InvalidKey +from cryptography.fernet import Fernet, InvalidToken +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC +from nacl import pwhash, secret +from password_strength import PasswordPolicy +from substrateinterface.utils.ss58 import ss58_encode +from termcolor import colored +from rich.prompt import Confirm + + +NACL_SALT = b"\x13q\x83\xdf\xf1Z\t\xbc\x9c\x90\xb5Q\x879\xe9\xb1" + + +def serialized_keypair_to_keyfile_data(keypair: "bittensor.Keypair") -> bytes: + """Serializes keypair object into keyfile data. + Args: + keypair (bittensor.Keypair): The keypair object to be serialized. + Returns: + data (bytes): Serialized keypair data. + """ + json_data = { + "accountId": "0x" + keypair.public_key.hex() if keypair.public_key else None, + "publicKey": "0x" + keypair.public_key.hex() if keypair.public_key else None, + "secretPhrase": keypair.mnemonic if keypair.mnemonic else None, + "secretSeed": "0x" + + ( + keypair.seed_hex + if isinstance(keypair.seed_hex, str) + else keypair.seed_hex.hex() + ) + if keypair.seed_hex + else None, + "ss58Address": keypair.ss58_address if keypair.ss58_address else None, + } + data = json.dumps(json_data).encode() + return data + + +def deserialize_keypair_from_keyfile_data(keyfile_data: bytes) -> "bittensor.Keypair": + """Deserializes Keypair object from passed keyfile data. + Args: + keyfile_data (bytes): The keyfile data as bytes to be loaded. + Returns: + keypair (bittensor.Keypair): The Keypair loaded from bytes. + Raises: + KeyFileError: Raised if the passed bytes cannot construct a keypair object. + """ + keyfile_data = keyfile_data.decode() + try: + keyfile_dict = dict(json.loads(keyfile_data)) + except: + string_value = str(keyfile_data) + if string_value[:2] == "0x": + string_value = ss58_encode(string_value) + keyfile_dict = { + "accountId": None, + "publicKey": None, + "secretPhrase": None, + "secretSeed": None, + "ss58Address": string_value, + } + else: + raise bittensor.KeyFileError( + "Keypair could not be created from keyfile data: {}".format( + string_value + ) + ) + + if "secretSeed" in keyfile_dict and keyfile_dict["secretSeed"] is not None: + return bittensor.Keypair.create_from_seed(keyfile_dict["secretSeed"]) + + if "secretPhrase" in keyfile_dict and keyfile_dict["secretPhrase"] is not None: + return bittensor.Keypair.create_from_mnemonic( + mnemonic=keyfile_dict["secretPhrase"] + ) + + if "ss58Address" in keyfile_dict and keyfile_dict["ss58Address"] is not None: + return bittensor.Keypair(ss58_address=keyfile_dict["ss58Address"]) + + else: + raise bittensor.KeyFileError( + "Keypair could not be created from keyfile data: {}".format(keyfile_dict) + ) + + +def validate_password(password: str) -> bool: + """Validates the password against a password policy. + Args: + password (str): The password to verify. + Returns: + valid (bool): True if the password meets validity requirements. + """ + policy = PasswordPolicy.from_names(strength=0.20, entropybits=10, length=6) + if not password: + return False + tested_pass = policy.password(password) + result = tested_pass.test() + if len(result) > 0: + print( + colored( + "Password not strong enough. Try increasing the length of the password or the password complexity" + ) + ) + return False + password_verification = getpass.getpass("Retype your password: ") + if password != password_verification: + print("Passwords do not match") + return False + return True + + +def ask_password_to_encrypt() -> str: + """Prompts the user to enter a password for key encryption. + Returns: + password (str): The valid password entered by the user. + """ + valid = False + while not valid: + password = getpass.getpass("Specify password for key encryption: ") + valid = validate_password(password) + return password + + +def keyfile_data_is_encrypted_nacl(keyfile_data: bytes) -> bool: + """Returns true if the keyfile data is NaCl encrypted. + Args: + keyfile_data ( bytes, required ): + Bytes to validate + Returns: + is_nacl (bool): + True if data is ansible encrypted. + """ + return keyfile_data[: len("$NACL")] == b"$NACL" + + +def keyfile_data_is_encrypted_ansible(keyfile_data: bytes) -> bool: + """Returns true if the keyfile data is ansible encrypted. + Args: + keyfile_data (bytes): The bytes to validate. + Returns: + is_ansible (bool): True if the data is ansible encrypted. + """ + return keyfile_data[:14] == b"$ANSIBLE_VAULT" + + +def keyfile_data_is_encrypted_legacy(keyfile_data: bytes) -> bool: + """Returns true if the keyfile data is legacy encrypted. + Args: + keyfile_data (bytes): The bytes to validate. + Returns: + is_legacy (bool): True if the data is legacy encrypted. + """ + return keyfile_data[:6] == b"gAAAAA" + + +def keyfile_data_is_encrypted(keyfile_data: bytes) -> bool: + """Returns true if the keyfile data is encrypted. + Args: + keyfile_data (bytes): The bytes to validate. + Returns: + is_encrypted (bool): True if the data is encrypted. + """ + return ( + keyfile_data_is_encrypted_nacl(keyfile_data) + or keyfile_data_is_encrypted_ansible(keyfile_data) + or keyfile_data_is_encrypted_legacy(keyfile_data) + ) + + +def keyfile_data_encryption_method(keyfile_data: bytes) -> bool: + """Returns true if the keyfile data is encrypted. + Args: + keyfile_data ( bytes, required ): + Bytes to validate + Returns: + encryption_method (bool): + True if data is encrypted. + """ + + if keyfile_data_is_encrypted_nacl(keyfile_data): + return "NaCl" + elif keyfile_data_is_encrypted_ansible(keyfile_data): + return "Ansible Vault" + elif keyfile_data_is_encrypted_legacy(keyfile_data): + return "legacy" + + +def legacy_encrypt_keyfile_data(keyfile_data: bytes, password: str = None) -> bytes: + password = ask_password_to_encrypt() if password is None else password + console = bittensor.__console__ + with console.status( + ":exclamation_mark: Encrypting key with legacy encrpytion method..." + ): + vault = Vault(password) + return vault.vault.encrypt(keyfile_data) + + +def encrypt_keyfile_data(keyfile_data: bytes, password: str = None) -> bytes: + """Encrypts the passed keyfile data using ansible vault. + Args: + keyfile_data (bytes): The bytes to encrypt. + password (str, optional): The password used to encrypt the data. If None, asks for user input. + Returns: + encrypted_data (bytes): The encrypted data. + """ + password = bittensor.ask_password_to_encrypt() if password is None else password + password = bytes(password, "utf-8") + kdf = pwhash.argon2i.kdf + key = kdf( + secret.SecretBox.KEY_SIZE, + password, + NACL_SALT, + opslimit=pwhash.argon2i.OPSLIMIT_SENSITIVE, + memlimit=pwhash.argon2i.MEMLIMIT_SENSITIVE, + ) + box = secret.SecretBox(key) + encrypted = box.encrypt(keyfile_data) + return b"$NACL" + encrypted + + +def get_coldkey_password_from_environment(coldkey_name: str) -> Optional[str]: + """Retrieves the cold key password from the environment variables. + Args: + coldkey_name (str): The name of the cold key. + Returns: + password (str): The password retrieved from the environment variables, or None if not found. + """ + for env_var in os.environ: + if env_var.upper().startswith("BT_COLD_PW_") and env_var.upper().endswith( + coldkey_name.upper() + ): + return os.getenv(env_var) + + return None + + +def decrypt_keyfile_data( + keyfile_data: bytes, password: str = None, coldkey_name: Optional[str] = None +) -> bytes: + """Decrypts the passed keyfile data using ansible vault. + Args: + keyfile_data (bytes): The bytes to decrypt. + password (str, optional): The password used to decrypt the data. If None, asks for user input. + coldkey_name (str, optional): The name of the cold key. If provided, retrieves the password from environment variables. + Returns: + decrypted_data (bytes): The decrypted data. + Raises: + KeyFileError: Raised if the file is corrupted or if the password is incorrect. + """ + if coldkey_name is not None and password is None: + password = get_coldkey_password_from_environment(coldkey_name) + + try: + password = ( + getpass.getpass("Enter password to unlock key: ") + if password is None + else password + ) + console = bittensor.__console__ + with console.status(":key: Decrypting key..."): + # NaCl SecretBox decrypt. + if keyfile_data_is_encrypted_nacl(keyfile_data): + password = bytes(password, "utf-8") + kdf = pwhash.argon2i.kdf + key = kdf( + secret.SecretBox.KEY_SIZE, + password, + NACL_SALT, + opslimit=pwhash.argon2i.OPSLIMIT_SENSITIVE, + memlimit=pwhash.argon2i.MEMLIMIT_SENSITIVE, + ) + box = secret.SecretBox(key) + decrypted_keyfile_data = box.decrypt(keyfile_data[len("$NACL") :]) + # Ansible decrypt. + elif keyfile_data_is_encrypted_ansible(keyfile_data): + vault = Vault(password) + try: + decrypted_keyfile_data = vault.load(keyfile_data) + except AnsibleVaultError: + raise bittensor.KeyFileError("Invalid password") + # Legacy decrypt. + elif keyfile_data_is_encrypted_legacy(keyfile_data): + __SALT = ( + b"Iguesscyborgslikemyselfhaveatendencytobeparanoidaboutourorigins" + ) + kdf = PBKDF2HMAC( + algorithm=hashes.SHA256(), + salt=__SALT, + length=32, + iterations=10000000, + backend=default_backend(), + ) + key = base64.urlsafe_b64encode(kdf.derive(password.encode())) + cipher_suite = Fernet(key) + decrypted_keyfile_data = cipher_suite.decrypt(keyfile_data) + # Unknown. + else: + raise bittensor.KeyFileError( + "keyfile data: {} is corrupt".format(keyfile_data) + ) + + except (InvalidSignature, InvalidKey, InvalidToken): + raise bittensor.KeyFileError("Invalid password") + + if not isinstance(decrypted_keyfile_data, bytes): + decrypted_keyfile_data = json.dumps(decrypted_keyfile_data).encode() + return decrypted_keyfile_data + + +class keyfile: + """Defines an interface for a substrate interface keypair stored on device.""" + + def __init__(self, path: str): + self.path = os.path.expanduser(path) + self.name = Path(self.path).parent.stem + + def __str__(self): + if not self.exists_on_device(): + return "keyfile (empty, {})>".format(self.path) + if self.is_encrypted(): + return "Keyfile ({} encrypted, {})>".format( + keyfile_data_encryption_method(self._read_keyfile_data_from_file()), + self.path, + ) + else: + return "keyfile (decrypted, {})>".format(self.path) + + def __repr__(self): + return self.__str__() + + @property + def keypair(self) -> "bittensor.Keypair": + """Returns the keypair from path, decrypts data if the file is encrypted. + Returns: + keypair (bittensor.Keypair): The keypair stored under the path. + Raises: + KeyFileError: Raised if the file does not exist, is not readable, writable, corrupted, or if the password is incorrect. + """ + return self.get_keypair() + + @property + def data(self) -> bytes: + """Returns the keyfile data under path. + Returns: + keyfile_data (bytes): The keyfile data stored under the path. + Raises: + KeyFileError: Raised if the file does not exist, is not readable, or writable. + """ + return self._read_keyfile_data_from_file() + + @property + def keyfile_data(self) -> bytes: + """Returns the keyfile data under path. + Returns: + keyfile_data (bytes): The keyfile data stored under the path. + Raises: + KeyFileError: Raised if the file does not exist, is not readable, or writable. + """ + return self._read_keyfile_data_from_file() + + def set_keypair( + self, + keypair: "bittensor.Keypair", + encrypt: bool = True, + overwrite: bool = False, + password: str = None, + ): + """Writes the keypair to the file and optionally encrypts data. + Args: + keypair (bittensor.Keypair): The keypair to store under the path. + encrypt (bool, optional): If True, encrypts the file under the path. Default is True. + overwrite (bool, optional): If True, forces overwrite of the current file. Default is False. + password (str, optional): The password used to encrypt the file. If None, asks for user input. + Raises: + KeyFileError: Raised if the file does not exist, is not readable, writable, or if the password is incorrect. + """ + self.make_dirs() + keyfile_data = serialized_keypair_to_keyfile_data(keypair) + if encrypt: + keyfile_data = bittensor.encrypt_keyfile_data(keyfile_data, password) + self._write_keyfile_data_to_file(keyfile_data, overwrite=overwrite) + + def get_keypair(self, password: str = None) -> "bittensor.Keypair": + """Returns the keypair from the path, decrypts data if the file is encrypted. + Args: + password (str, optional): The password used to decrypt the file. If None, asks for user input. + Returns: + keypair (bittensor.Keypair): The keypair stored under the path. + Raises: + KeyFileError: Raised if the file does not exist, is not readable, writable, corrupted, or if the password is incorrect. + """ + keyfile_data = self._read_keyfile_data_from_file() + if keyfile_data_is_encrypted(keyfile_data): + decrypted_keyfile_data = decrypt_keyfile_data( + keyfile_data, password, coldkey_name=self.name + ) + else: + decrypted_keyfile_data = keyfile_data + return deserialize_keypair_from_keyfile_data(decrypted_keyfile_data) + + def make_dirs(self): + """Creates directories for the path if they do not exist.""" + directory = os.path.dirname(self.path) + if not os.path.exists(directory): + os.makedirs(directory) + + def exists_on_device(self) -> bool: + """Returns True if the file exists on the device. + Returns: + on_device (bool): True if the file is on the device. + """ + if not os.path.isfile(self.path): + return False + return True + + def is_readable(self) -> bool: + """Returns True if the file under path is readable. + Returns: + readable (bool): True if the file is readable. + """ + if not self.exists_on_device(): + return False + if not os.access(self.path, os.R_OK): + return False + return True + + def is_writable(self) -> bool: + """Returns True if the file under path is writable. + Returns: + writable (bool): True if the file is writable. + """ + if os.access(self.path, os.W_OK): + return True + return False + + def is_encrypted(self) -> bool: + """Returns True if the file under path is encrypted. + Returns: + encrypted (bool): True if the file is encrypted. + """ + if not self.exists_on_device(): + return False + if not self.is_readable(): + return False + return keyfile_data_is_encrypted(self._read_keyfile_data_from_file()) + + def _may_overwrite(self) -> bool: + """Asks the user if it's okay to overwrite the file. + Returns: + may_overwrite (bool): True if the user allows overwriting the file. + """ + choice = input("File {} already exists. Overwrite? (y/N) ".format(self.path)) + return choice == "y" + + def check_and_update_encryption( + self, print_result: bool = True, no_prompt: bool = False + ): + """Check the version of keyfile and update if needed. + Args: + print_result (bool): + Print the checking result or not. + no_prompt (bool): + Skip if no prompt. + Raises: + KeyFileError: + Raised if the file does not exists, is not readable, writable. + Returns: + result (bool): + return True if the keyfile is the most updated with nacl, else False. + """ + if not self.exists_on_device(): + if print_result: + bittensor.__console__.print(f"Keyfile does not exist. {self.path}") + return False + if not self.is_readable(): + if print_result: + bittensor.__console__.print(f"Keyfile is not redable. {self.path}") + return False + if not self.is_writable(): + if print_result: + bittensor.__console__.print(f"Keyfile is not writable. {self.path}") + return False + + update_keyfile = False + if not no_prompt: + keyfile_data = self._read_keyfile_data_from_file() + + # If the key is not nacl encrypted. + if keyfile_data_is_encrypted( + keyfile_data + ) and not keyfile_data_is_encrypted_nacl(keyfile_data): + terminate = False + bittensor.__console__.print( + f"You may update the keyfile to improve the security for storing your keys.\nWhile the key and the password stays the same, it would require providing your password once.\n:key:{self}\n" + ) + update_keyfile = Confirm.ask("Update keyfile?") + if update_keyfile: + stored_mnemonic = False + while not stored_mnemonic: + bittensor.__console__.print( + f"\nPlease make sure you have the mnemonic stored in case an error occurs during the transfer.", + style="white on red", + ) + stored_mnemonic = Confirm.ask("Have you stored the mnemonic?") + if not stored_mnemonic and not Confirm.ask( + "You must proceed with a stored mnemonic, retry and continue this keyfile update?" + ): + terminate = True + break + + decrypted_keyfile_data = None + while decrypted_keyfile_data == None and not terminate: + try: + password = getpass.getpass( + "\nEnter password to update keyfile: " + ) + decrypted_keyfile_data = decrypt_keyfile_data( + keyfile_data, coldkey_name=self.name, password=password + ) + except KeyFileError: + if not Confirm.ask( + "Invalid password, retry and continue this keyfile update?" + ): + terminate = True + break + + if not terminate: + encrypted_keyfile_data = encrypt_keyfile_data( + decrypted_keyfile_data, password=password + ) + self._write_keyfile_data_to_file( + encrypted_keyfile_data, overwrite=True + ) + + if print_result or update_keyfile: + keyfile_data = self._read_keyfile_data_from_file() + if not keyfile_data_is_encrypted(keyfile_data): + if print_result: + bittensor.__console__.print( + f"\nKeyfile is not encrypted. \n:key: {self}" + ) + return False + elif keyfile_data_is_encrypted_nacl(keyfile_data): + if print_result: + bittensor.__console__.print( + f"\n:white_heavy_check_mark: Keyfile is updated. \n:key: {self}" + ) + return True + else: + if print_result: + bittensor.__console__.print( + f'\n:cross_mark: Keyfile is outdated, please update with "btcli wallet update" \n:key: {self}' + ) + return False + return False + + def encrypt(self, password: str = None): + """Encrypts the file under the path. + Args: + password (str, optional): The password for encryption. If None, asks for user input. + Raises: + KeyFileError: Raised if the file does not exist, is not readable, or writable. + """ + if not self.exists_on_device(): + raise bittensor.KeyFileError( + "Keyfile at: {} does not exist".format(self.path) + ) + if not self.is_readable(): + raise bittensor.KeyFileError( + "Keyfile at: {} is not readable".format(self.path) + ) + if not self.is_writable(): + raise bittensor.KeyFileError( + "Keyfile at: {} is not writable".format(self.path) + ) + keyfile_data = self._read_keyfile_data_from_file() + if not keyfile_data_is_encrypted(keyfile_data): + as_keypair = deserialize_keypair_from_keyfile_data(keyfile_data) + keyfile_data = serialized_keypair_to_keyfile_data(as_keypair) + keyfile_data = encrypt_keyfile_data(keyfile_data, password) + self._write_keyfile_data_to_file(keyfile_data, overwrite=True) + + def decrypt(self, password: str = None): + """Decrypts the file under the path. + Args: + password (str, optional): The password for decryption. If None, asks for user input. + Raises: + KeyFileError: Raised if the file does not exist, is not readable, writable, corrupted, or if the password is incorrect. + """ + if not self.exists_on_device(): + raise bittensor.KeyFileError( + "Keyfile at: {} does not exist".format(self.path) + ) + if not self.is_readable(): + raise bittensor.KeyFileError( + "Keyfile at: {} is not readable".format(self.path) + ) + if not self.is_writable(): + raise bittensor.KeyFileError( + "Keyfile at: {} is not writable".format(self.path) + ) + keyfile_data = self._read_keyfile_data_from_file() + if keyfile_data_is_encrypted(keyfile_data): + keyfile_data = decrypt_keyfile_data( + keyfile_data, password, coldkey_name=self.name + ) + as_keypair = deserialize_keypair_from_keyfile_data(keyfile_data) + keyfile_data = serialized_keypair_to_keyfile_data(as_keypair) + self._write_keyfile_data_to_file(keyfile_data, overwrite=True) + + def _read_keyfile_data_from_file(self) -> bytes: + """Reads the keyfile data from the file. + Returns: + keyfile_data (bytes): The keyfile data stored under the path. + Raises: + KeyFileError: Raised if the file does not exist or is not readable. + """ + if not self.exists_on_device(): + raise bittensor.KeyFileError( + "Keyfile at: {} does not exist".format(self.path) + ) + if not self.is_readable(): + raise bittensor.KeyFileError( + "Keyfile at: {} is not readable".format(self.path) + ) + with open(self.path, "rb") as file: + data = file.read() + return data + + def _write_keyfile_data_to_file(self, keyfile_data: bytes, overwrite: bool = False): + """Writes the keyfile data to the file. + Args: + keyfile_data (bytes): The byte data to store under the path. + overwrite (bool, optional): If True, overwrites the data without asking for permission from the user. Default is False. + Raises: + KeyFileError: Raised if the file is not writable or the user responds No to the overwrite prompt. + """ + # Check overwrite. + if self.exists_on_device() and not overwrite: + if not self._may_overwrite(): + raise bittensor.KeyFileError( + "Keyfile at: {} is not writable".format(self.path) + ) + with open(self.path, "wb") as keyfile: + keyfile.write(keyfile_data) + # Set file permissions. + os.chmod(self.path, stat.S_IRUSR | stat.S_IWUSR) + + +class Mockkeyfile: + """ + The Mockkeyfile is a mock object representing a keyfile that does not exist on the device. + It is designed for use in testing scenarios and simulations where actual filesystem operations are not required. + The keypair stored in the Mockkeyfile is treated as non-encrypted and the data is stored as a serialized string. + """ + + def __init__(self, path: str): + """ + Initializes a Mockkeyfile object. + + Args: + path (str): The path of the mock keyfile. + """ + self.path = path + self._mock_keypair = None + self._mock_data = None + + def __str__(self): + """ + Returns a string representation of the Mockkeyfile. The representation will indicate if the + keyfile is empty, encrypted, or decrypted. + + Returns: + str: The string representation of the Mockkeyfile. + """ + return f"Mockkeyfile({self.path})" + + def __repr__(self): + """ + Returns a string representation of the Mockkeyfile, same as __str__(). + + Returns: + str: The string representation of the Mockkeyfile. + """ + return self.__str__() + + @property + def keypair(self): + """ + Returns the mock keypair stored in the keyfile. + + Returns: + bittensor.Keypair: The mock keypair. + """ + return self._mock_keypair + + @property + def data(self): + """ + Returns the serialized keypair data stored in the keyfile. + + Returns: + bytes: The serialized keypair data. + """ + return self._mock_data + + def set_keypair(self, keypair, encrypt=True, overwrite=False, password=None): + """ + Sets the mock keypair in the keyfile. The `encrypt` and `overwrite` parameters are ignored. + + Args: + keypair (bittensor.Keypair): The mock keypair to be set. + encrypt (bool, optional): Ignored in this context. Defaults to True. + overwrite (bool, optional): Ignored in this context. Defaults to False. + password (str, optional): Ignored in this context. Defaults to None. + """ + self._mock_keypair = keypair + self._mock_data = None # You may need to serialize the keypair here + + def get_keypair(self, password=None): + """ + Returns the mock keypair stored in the keyfile. The `password` parameter is ignored. + + Args: + password (str, optional): Ignored in this context. Defaults to None. + + Returns: + bittensor.Keypair: The mock keypair stored in the keyfile. + """ + return self._mock_keypair + + def make_dirs(self): + """ + Creates the directories for the mock keyfile. Does nothing in this class, + since no actual filesystem operations are needed. + """ + pass + + def exists_on_device(self): + """ + Returns True indicating that the mock keyfile exists on the device (although + it is not created on the actual file system). + + Returns: + bool: Always returns True for Mockkeyfile. + """ + return True + + def is_readable(self): + """ + Returns True indicating that the mock keyfile is readable (although it is not + read from the actual file system). + + Returns: + bool: Always returns True for Mockkeyfile. + """ + return True + + def is_writable(self): + """ + Returns True indicating that the mock keyfile is writable (although it is not + written to the actual file system). + + Returns: + bool: Always returns True for Mockkeyfile. + """ + return True + + def is_encrypted(self): + """ + Returns False indicating that the mock keyfile is not encrypted. + + Returns: + bool: Always returns False for Mockkeyfile. + """ + return False + + def encrypt(self, password=None): + """ + Raises a ValueError since encryption is not supported for the mock keyfile. + + Args: + password (str, optional): Ignored in this context. Defaults to None. + + Raises: + ValueError: Always raises this exception for Mockkeyfile. + """ + raise ValueError("Cannot encrypt a Mockkeyfile") + + def decrypt(self, password=None): + """ + Returns without doing anything since the mock keyfile is not encrypted. + + Args: + password (str, optional): Ignored in this context. Defaults to None. + """ + pass + + def check_and_update_encryption(self, no_prompt=None, print_result=False): + return diff --git a/bittensor/_logging/__init__.py b/bittensor/logging.py similarity index 64% rename from bittensor/_logging/__init__.py rename to bittensor/logging.py index 44bf6f2473..87aeb5d190 100644 --- a/bittensor/_logging/__init__.py +++ b/bittensor/logging.py @@ -23,7 +23,6 @@ import torch import argparse import bittensor -import bittensor.utils.codes as codes from loguru import logger @@ -53,7 +52,7 @@ class logging: def __new__( cls, - config: "bittensor.Config" = None, + config: "bittensor.config" = None, debug: bool = None, trace: bool = None, record_log: bool = None, @@ -61,7 +60,7 @@ def __new__( ): r"""Instantiate bittensor logging system backend. Args: - config (:obj:`bittensor.Config`, `optional`): + config (:obj:`bittensor.config`, `optional`): bittensor.logging.config() debug (:obj:`bool`, `optional`): Turn on debug. @@ -135,7 +134,7 @@ def config(cls): """ parser = argparse.ArgumentParser() logging.add_args(parser) - return bittensor.config(parser) + return bittensor.config(parser, args=[]) @classmethod def help(cls): @@ -149,66 +148,43 @@ def help(cls): def add_args(cls, parser: argparse.ArgumentParser, prefix: str = None): """Accept specific arguments fro parser""" prefix_str = "" if prefix == None else prefix + "." - if prefix is not None: - if bittensor.defaults.get(prefix, d=None) == None: - setattr(bittensor.defaults, prefix, bittensor.Config()) - getattr(bittensor.defaults, prefix).logging = bittensor.defaults.logging try: + default_logging_debug = os.getenv("BT_LOGGING_DEBUG") or False + default_logging_trace = os.getenv("BT_LOGGING_TRACE") or False + default_logging_record_log = os.getenv("BT_LOGGING_RECORD_LOG") or False + default_logging_logging_dir = ( + os.getenv("BT_LOGGING_LOGGING_DIR") or "~/.bittensor/miners" + ) parser.add_argument( "--" + prefix_str + "logging.debug", action="store_true", help="""Turn on bittensor debugging information""", - default=bittensor.defaults.logging.debug, + default=default_logging_debug, ) parser.add_argument( "--" + prefix_str + "logging.trace", action="store_true", help="""Turn on bittensor trace level information""", - default=bittensor.defaults.logging.trace, + default=default_logging_trace, ) parser.add_argument( "--" + prefix_str + "logging.record_log", action="store_true", help="""Turns on logging to file.""", - default=bittensor.defaults.logging.record_log, + default=default_logging_record_log, ) parser.add_argument( "--" + prefix_str + "logging.logging_dir", type=str, help="Logging default root directory.", - default=bittensor.defaults.logging.logging_dir, + default=default_logging_logging_dir, ) except argparse.ArgumentError: # re-parsing arguments. pass @classmethod - def add_defaults(cls, defaults): - """Adds parser defaults to object from enviroment variables.""" - defaults.logging = bittensor.Config() - defaults.logging.debug = ( - os.getenv("BT_LOGGING_DEBUG") - if os.getenv("BT_LOGGING_DEBUG") != None - else False - ) - defaults.logging.trace = ( - os.getenv("BT_LOGGING_TRACE") - if os.getenv("BT_LOGGING_DEBUG") != None - else False - ) - defaults.logging.record_log = ( - os.getenv("BT_LOGGING_RECORD_LOG") - if os.getenv("BT_LOGGING_RECORD_LOG") != None - else False - ) - defaults.logging.logging_dir = ( - os.getenv("BT_LOGGING_LOGGING_DIR") - if os.getenv("BT_LOGGING_LOGGING_DIR") != None - else "~/.bittensor/miners" - ) - - @classmethod - def check_config(cls, config: "bittensor.Config"): + def check_config(cls, config: "bittensor.config"): """Check config""" assert config.logging @@ -248,102 +224,14 @@ def log_save_filter(cls, record): @classmethod def log_formatter(cls, record): """Log with different format according to record['extra']""" - extra = record["extra"] - if "rpc" in extra: - return ( - "{time:YYYY-MM-DD HH:mm:ss.SSS} | " - + extra["code_str"] - + " | {extra[prefix]} | {extra[direction]} | {extra[arrow]} | {extra[uid_str]} | {extra[inputs]} | {extra[call_time]} | {extra[key_str]} | {extra[rpc_message]} | {extra[synapse]} \n" - ) - else: - return "{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: ^16} | {message}\n" + return "{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: ^16} | {message}\n" @classmethod def log_save_formatter(cls, record): - extra = record["extra"] - if "rpc" in extra: - return ( - "{time:YYYY-MM-DD HH:mm:ss.SSS} | " - + extra["code_str"] - + " | {extra[prefix]} | {extra[direction]} | {extra[arrow]} | {extra[uid_str]} | {extra[inputs]} | {extra[call_time]} | {extra[key_str]} | {extra[rpc_message]} \n" - ) - else: - if cls.__trace_on__: - return "{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: ^16} | {name}:{function}:{line} | {message}\n" - else: - return "{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: ^16} | {message}\n" - - @classmethod - def rpc_log( - cls, - axon: bool, - forward: bool, - is_response: bool, - code: int, - call_time: float, - pubkey: str, - uid: int = None, - inputs: list = None, - outputs: list = None, - message: str = "", - synapse: "bittensor.Synapse" = None, - ): - """Debug logging for the communication between endpoints with axon/dendrite""" - - if axon: - prefix = "Synapse" - else: - prefix = "Dendrite" - prefix = prefix.center(len("Dendrite")) - - if forward: - direction = "Forward" - else: - direction = "Backward" - direction = direction.center(len("Backward")) - - if is_response: - arrow = "<---" + if cls.__trace_on__: + return "{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: ^16} | {name}:{function}:{line} | {message}\n" else: - arrow = "--->" - - key_str = "{}".format(pubkey) - call_time_str = "{:.2f}s".format(call_time).center(6) - - if uid != None: - uid_str = str(uid).center(5) - else: - uid_str = "-".center(5) - - code_color = codes.code_to_loguru_color(code) - code_string = codes.code_to_string(code) - code_string = code_string.center(16) - code_str = "<" + code_color + ">" + code_string + "" - - if is_response: - inputs = str(list(outputs)) if outputs != None else "[x]" - else: - inputs = str(list(inputs)) if inputs != None else "[x]" - inputs = inputs.center(15) - - if synapse != None: - synapse = codes.code_to_synapse(synapse) - - rpc_message = message if message != None else "None" - logger.debug( - "rpc", - rpc=True, - prefix=prefix, - direction=direction, - arrow=arrow, - call_time=call_time_str, - uid_str=uid_str, - key_str=key_str, - code_str=code_str, - inputs=inputs, - rpc_message=rpc_message, - synapse=synapse, - ) + return "{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: ^16} | {message}\n" @classmethod def _format(cls, prefix: object, sufix: object = None): diff --git a/bittensor/metagraph.py b/bittensor/metagraph.py new file mode 100644 index 0000000000..2c31ccf6b1 --- /dev/null +++ b/bittensor/metagraph.py @@ -0,0 +1,653 @@ +# The MIT License (MIT) +# Copyright © 2021 Yuma Rao +# Copyright © 2023 Opentensor Foundation +# Copyright © 2023 Opentensor Technologies Inc + +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the “Software”), to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of +# the Software. + +# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# 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 os +import torch +import bittensor +from os import listdir +from os.path import join +from typing import List, Optional + + +def get_save_dir(network: str, netuid: int) -> str: + """ + Return directory path from network and netuid. + + Args: + network (str): Network name. + netuid (int): Network UID. + + Returns: + str: Directory path. + """ + return os.path.expanduser( + f"~/.bittensor/metagraphs/network-{str(network)}/netuid-{str(netuid)}/" + ) + + +def latest_block_path(dir_path: str) -> int: + """ + Get the latest block path from the directory. + + Args: + dir_path (str): Directory path. + + Returns: + int: Latest block path. + """ + latest_block = -1 + latest_file_full_path = None + for filename in listdir(dir_path): + full_path_filename = os.path.expanduser(join(dir_path, filename)) + try: + block_number = int(filename.split("-")[1].split(".")[0]) + if block_number > latest_block: + latest_block = block_number + latest_file_full_path = full_path_filename + except Exception as e: + pass + if not latest_file_full_path: + raise ValueError(f"Metagraph not found at: {dir_path}") + else: + return latest_file_full_path + + +class metagraph(torch.nn.Module): + """ + Metagraph class representing the neural network graph. + + Attributes: + netuid (int): Network UID. + network (str): Network name. + version (torch.nn.Parameter): Version of the network. + n (torch.nn.Parameter): Number of neurons in the graph. + block (torch.nn.Parameter): Current block number. + stake (torch.nn.Parameter): Stake of the neurons. + total_stake (torch.nn.Parameter): Total stake of the neurons. + ranks (torch.nn.Parameter): Ranks of the neurons. + trust (torch.nn.Parameter): Trust values of the neurons. + consensus (torch.nn.Parameter): Consensus values of the neurons. + validator_trust (torch.nn.Parameter): Validator trust values of the neurons. + incentive (torch.nn.Parameter): Incentive values of the neurons. + emission (torch.nn.Parameter): Emission values of the neurons. + dividends (torch.nn.Parameter): Dividends of the neurons. + active (torch.nn.Parameter): Activation state of the neurons. + last_update (torch.nn.Parameter): Last update time of the neurons. + validator_permit (torch.nn.Parameter): Validator permit state of the neurons. + weights (torch.nn.Parameter): Weights of the neurons. + bonds (torch.nn.Parameter): Bonds of the neurons. + uids (torch.nn.Parameter): UID values of the neurons. + axons (List): List of axon information for the neurons. + """ + + @property + def S(self) -> torch.FloatTensor: + """ + Total stake of the neurons. + + Returns: + torch.FloatTensor: Total stake. + """ + return self.total_stake + + @property + def R(self) -> torch.FloatTensor: + """ + Ranks of the neurons. + + Returns: + torch.FloatTensor: Ranks. + """ + return self.ranks + + @property + def I(self) -> torch.FloatTensor: + """ + Incentive values of the neurons. + + Returns: + torch.FloatTensor: Incentive values. + """ + return self.incentive + + @property + def E(self) -> torch.FloatTensor: + """ + Emission values of the neurons. + + Returns: + torch.FloatTensor: Emission values. + """ + return self.emission + + @property + def C(self) -> torch.FloatTensor: + """ + Consensus values of the neurons. + + Returns: + torch.FloatTensor: Consensus values. + """ + return self.consensus + + @property + def T(self) -> torch.FloatTensor: + """ + Trust values of the neurons. + + Returns: + torch.FloatTensor: Trust values. + """ + return self.trust + + @property + def Tv(self) -> torch.FloatTensor: + """ + Validator trust values of the neurons. + + Returns: + torch.FloatTensor: Validator trust values. + """ + return self.validator_trust + + @property + def D(self) -> torch.FloatTensor: + """ + Dividends of the neurons. + + Returns: + torch.FloatTensor: Dividends. + """ + return self.dividends + + @property + def B(self) -> torch.FloatTensor: + """ + Bonds of the neurons. + + Returns: + torch.FloatTensor: Bonds. + """ + return self.bonds + + @property + def W(self) -> torch.FloatTensor: + """ + Weights of the neurons. + + Returns: + torch.FloatTensor: Weights. + """ + return self.weights + + @property + def hotkeys(self) -> List[str]: + """ + List of hotkeys for the neurons. + + Returns: + List[str]: List of hotkeys. + """ + return [axon.hotkey for axon in self.axons] + + @property + def coldkeys(self) -> List[str]: + """ + List of coldkeys for the neurons. + + Returns: + List[str]: List of coldkeys. + """ + return [axon.coldkey for axon in self.axons] + + @property + def addresses(self) -> List[str]: + """ + List of IP addresses for the neurons. + + Returns: + List[str]: List of IP addresses. + """ + return [axon.ip_str() for axon in self.axons] + + def __str__(self) -> str: + """ + String representation of the metagraph. + + Returns: + str: String representation. + """ + return "metagraph(netuid:{}, n:{}, block:{}, network:{})".format( + self.netuid, self.n.item(), self.block.item(), self.network + ) + + def __repr__(self) -> str: + """ + String representation of the metagraph. + + Returns: + str: String representation. + """ + return self.__str__() + + def metadata(self) -> dict: + """ + Get the metadata of the metagraph. + + Returns: + dict: Metadata dictionary. + """ + return { + "netuid": self.netuid, + "n": self.n.item(), + "block": self.block.item(), + "network": self.network, + "version": bittensor.__version__, + } + + def __init__( + self, netuid: int, network: str = "finney", lite: bool = True, sync: bool = True + ) -> "metagraph": + """ + Initialize the metagraph object. + + Args: + netuid (int): Network UID. + network (str): Network name. + lite (bool): Whether to use lite version of the metagraph. + sync (bool): Whether to synchronize the metagraph. + """ + 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.tensor([0], dtype=torch.int64), requires_grad=False + ) + self.block = 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.tensor([], dtype=torch.float32), requires_grad=False + ) + self.ranks = torch.nn.Parameter( + torch.tensor([], dtype=torch.float32), requires_grad=False + ) + self.trust = torch.nn.Parameter( + torch.tensor([], dtype=torch.float32), requires_grad=False + ) + self.consensus = torch.nn.Parameter( + torch.tensor([], dtype=torch.float32), requires_grad=False + ) + self.validator_trust = torch.nn.Parameter( + torch.tensor([], dtype=torch.float32), requires_grad=False + ) + self.incentive = torch.nn.Parameter( + torch.tensor([], dtype=torch.float32), requires_grad=False + ) + self.emission = torch.nn.Parameter( + torch.tensor([], dtype=torch.float32), requires_grad=False + ) + self.dividends = 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.tensor([], dtype=torch.float32), requires_grad=False + ) + self.bonds = 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.axons = [] + if sync: + self.sync(block=None, lite=lite) + + def sync( + self, + block: Optional[int] = None, + lite: bool = True, + subtensor: Optional["bittensor.subtensor"] = None, + root: bool = False, + ) -> "metagraph": + """ + Initiates the synchronization process of the metagraph. + + Args: + block (int, optional): Block number to sync. If None, the current block is used. + lite (bool): Whether to use lite version of the metagraph. + subtensor (bittensor.subtensor, optional): Subtensor object to use for syncing. + + Returns: + metagraph: Updated metagraph object. + """ + # Initialize subtensor + subtensor = self._initialize_subtensor(subtensor) + + # Assign neurons based on 'lite' flag + self._assign_neurons(block, lite, subtensor) + + # Set attributes for metagraph + self._set_metagraph_attributes(block, subtensor) + + # If not a 'lite' version, compute and set weights and bonds for each neuron + if not lite: + self._set_weights_and_bonds(root=root, subtensor=subtensor) + + def _initialize_subtensor(self, subtensor): + """ + Initializes the subtensor to be used for syncing. + + Args: + subtensor: The subtensor to initialize. If None, a new subtensor is created. + + Returns: + subtensor: The initialized subtensor. + """ + if not subtensor: + # TODO: Check and test the initialization of the new subtensor + subtensor = bittensor.subtensor(network=self.network) + return subtensor + + def _assign_neurons(self, block, lite, subtensor): + """ + Assigns neurons to the metagraph based on the 'lite' flag. + + Args: + block: The block number for which the neurons need to be assigned. + lite: Flag to decide the type of neurons to be assigned. + subtensor: The subtensor to use for syncing. + + Returns: + None. + """ + # TODO: Check and test the conditions for assigning neurons + if lite: + self.neurons = subtensor.neurons_lite(block=block, netuid=self.netuid) + else: + self.neurons = subtensor.neurons(block=block, netuid=self.netuid) + self.lite = lite + + def _set_metagraph_attributes(self, block, subtensor): + """ + Sets attributes for the metagraph. + + Args: + block: The block number for which the attributes need to be set. + subtensor: The subtensor to use for syncing. + + Returns: + None. + """ + # TODO: Check and test the setting of each attribute + self.n = self._create_tensor(len(self.neurons), dtype=torch.int64) + self.version = self._create_tensor( + [bittensor.__version_as_int__], dtype=torch.int64 + ) + self.block = self._create_tensor( + block if block else subtensor.block, dtype=torch.int64 + ) + self.uids = self._create_tensor( + [neuron.uid for neuron in self.neurons], dtype=torch.int64 + ) + self.trust = self._create_tensor( + [neuron.trust for neuron in self.neurons], dtype=torch.float32 + ) + self.consensus = self._create_tensor( + [neuron.consensus for neuron in self.neurons], dtype=torch.float32 + ) + self.incentive = self._create_tensor( + [neuron.incentive for neuron in self.neurons], dtype=torch.float32 + ) + self.dividends = self._create_tensor( + [neuron.dividends for neuron in self.neurons], dtype=torch.float32 + ) + self.ranks = self._create_tensor( + [neuron.rank for neuron in self.neurons], dtype=torch.float32 + ) + self.emission = self._create_tensor( + [neuron.emission for neuron in self.neurons], dtype=torch.float32 + ) + self.active = self._create_tensor( + [neuron.active for neuron in self.neurons], dtype=torch.int64 + ) + self.last_update = self._create_tensor( + [neuron.last_update for neuron in self.neurons], dtype=torch.int64 + ) + self.validator_permit = self._create_tensor( + [neuron.validator_permit for neuron in self.neurons], dtype=torch.bool + ) + self.validator_trust = self._create_tensor( + [neuron.validator_trust for neuron in self.neurons], dtype=torch.float32 + ) + self.total_stake = self._create_tensor( + [neuron.total_stake.tao for neuron in self.neurons], dtype=torch.float32 + ) + self.stake = self._create_tensor( + [neuron.stake for neuron in self.neurons], dtype=torch.float32 + ) + self.axons = [n.axon_info for n in self.neurons] + + def _create_tensor(self, data, dtype) -> torch.nn.Parameter: + """ + Creates a tensor parameter with the given data and dtype. + + Args: + data: The data to be included in the tensor. + dtype: The datatype for the tensor. + + Returns: + A tensor parameter. + """ + # TODO: Check and test the creation of tensor + return torch.nn.Parameter(torch.tensor(data, dtype=dtype), requires_grad=False) + + def _set_weights_and_bonds( + self, root: bool = False, subtensor: bittensor.subtensor = None + ): + """ + Computes and sets weights and bonds for each neuron. + + Returns: + None. + """ + # TODO: Check and test the computation of weights and bonds + if root: + self.weights = self._process_root_weights( + [neuron.weights for neuron in self.neurons], "weights", subtensor + ) + else: + self.weights = self._process_weights_or_bonds( + [neuron.weights for neuron in self.neurons], "weights" + ) + self.bonds = self._process_weights_or_bonds( + [neuron.bonds for neuron in self.neurons], "bonds" + ) + + def _process_weights_or_bonds(self, data, attribute: str) -> torch.nn.Parameter: + """ + Processes weights or bonds based on the given attribute. + + Args: + data: The weights or bonds data to be processed. + attribute: The attribute to decide the type of processing ('weights' or 'bonds'). + + Returns: + The processed tensor parameter. + """ + data_array = [] + for item in data: + if len(item) == 0: + data_array.append(torch.zeros(len(self.neurons))) + else: + uids, values = zip(*item) + # TODO: Validate and test the conversion of uids and values to tensor + if attribute == "weights": + data_array.append( + bittensor.utils.weight_utils.convert_weight_uids_and_vals_to_tensor( + len(self.neurons), uids, values + ) + ) + else: + data_array.append( + bittensor.utils.weight_utils.convert_bond_uids_and_vals_to_tensor( + len(self.neurons), uids, values + ) + ) + tensor_param = ( + torch.nn.Parameter(torch.stack(data_array), requires_grad=False) + if len(data_array) + else torch.nn.Parameter() + ) + if len(data_array) == 0: + bittensor.logging.warning( + f"Empty {attribute}_array on metagraph.sync(). The '{attribute}' tensor is empty." + ) + return tensor_param + + def _process_root_weights( + self, data, attribute: str, subtensor: bittensor.subtensor + ) -> torch.nn.Parameter: + """ + Processes root weights based on the given attribute. + + Args: + data: The weights or bonds data to be processed. + attribute: The attribute to decide the type of processing ('weights' or 'bonds'). + + Returns: + The processed tensor parameter. + """ + data_array = [] + n_subnets = subtensor.get_total_subnets() + subnets = subtensor.get_subnets() + for item in data: + if len(item) == 0: + data_array.append(torch.zeros(n_subnets)) + else: + uids, values = zip(*item) + # TODO: Validate and test the conversion of uids and values to tensor + data_array.append( + bittensor.utils.weight_utils.convert_root_weight_uids_and_vals_to_tensor( + n_subnets, uids, values, subnets + ) + ) + + tensor_param = ( + torch.nn.Parameter(torch.stack(data_array), requires_grad=False) + if len(data_array) + else torch.nn.Parameter() + ) + if len(data_array) == 0: + bittensor.logging.warning( + f"Empty {attribute}_array on metagraph.sync(). The '{attribute}' tensor is empty." + ) + return tensor_param + + def save(self) -> "metagraph": + """ + Save the state of the metagraph object. + + Returns: + metagraph: Updated metagraph object. + """ + 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" + state_dict = self.state_dict() + state_dict["axons"] = self.axons + torch.save(state_dict, graph_file) + state_dict = torch.load(graph_file) + return self + + def load(self) -> "metagraph": + """ + Load the state of the metagraph object. + + Returns: + metagraph: Updated metagraph object. + """ + self.load_from_path(get_save_dir(self.network, self.netuid)) + + def load_from_path(self, dir_path: str) -> "metagraph": + """ + Load the state of the metagraph object from the specified path. + + Args: + dir_path (str): Directory path. + + Returns: + metagraph: Updated metagraph object. + """ + 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) + self.axons = state_dict["axons"] + if "weights" in state_dict: + self.weights = torch.nn.Parameter( + state_dict["weights"], requires_grad=False + ) + if "bonds" in state_dict: + self.bonds = torch.nn.Parameter(state_dict["bonds"], requires_grad=False) + return self diff --git a/tests/integration_tests/test_priority_thread_pool.py b/bittensor/mock/__init__.py similarity index 65% rename from tests/integration_tests/test_priority_thread_pool.py rename to bittensor/mock/__init__.py index 0db6152812..b4f0efd5ca 100644 --- a/tests/integration_tests/test_priority_thread_pool.py +++ b/bittensor/mock/__init__.py @@ -1,5 +1,5 @@ # The MIT License (MIT) -# Copyright © 2021 Yuma Rao +# Copyright © 2023 Opentensor Technologies Inc # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated # documentation files (the “Software”), to deal in the Software without restriction, including without limitation @@ -15,24 +15,4 @@ # 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 bittensor -import unittest - - -class TestPriorityThreadPool(unittest.TestCase): - @classmethod - def setUpClass(cls) -> None: - cls.priority_pool = bittensor.prioritythreadpool(max_workers=1) - - def test_priority_thread_pool(self): - save = [] - - def save_number(number, save): - save += [number] - - with self.priority_pool: - for x in range(10): - self.priority_pool.submit(save_number, x, save, priority=x) - - assert save[0] == 0 - assert save[1] == 9 +from .subtensor_mock import MockSubtensor as MockSubtensor diff --git a/bittensor/mock/keyfile_mock.py b/bittensor/mock/keyfile_mock.py new file mode 100644 index 0000000000..e13126cc17 --- /dev/null +++ b/bittensor/mock/keyfile_mock.py @@ -0,0 +1,90 @@ +# The MIT License (MIT) + +# Copyright © 2021 Yuma Rao +# Copyright © 2022 Opentensor Foundation +# Copyright © 2023 Opentensor Technologies + +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the “Software”), to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of +# the Software. + +# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# 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. + +from bittensor import serialized_keypair_to_keyfile_data, keyfile, Keypair + + +class MockKeyfile(keyfile): + """Defines an interface to a mocked keyfile object (nothing is created on device) keypair is treated as non encrypted and the data is just the string version.""" + + def __init__(self, path: str): + super().__init__(path) + + self._mock_keypair = Keypair.create_from_mnemonic( + mnemonic="arrive produce someone view end scout bargain coil slight festival excess struggle" + ) + self._mock_data = serialized_keypair_to_keyfile_data(self._mock_keypair) + + def __str__(self): + if not self.exists_on_device(): + return "Keyfile (empty, {})>".format(self.path) + if self.is_encrypted(): + return "Keyfile (encrypted, {})>".format(self.path) + else: + return "Keyfile (decrypted, {})>".format(self.path) + + def __repr__(self): + return self.__str__() + + @property + def keypair(self) -> "Keypair": + return self._mock_keypair + + @property + def data(self) -> bytes: + return bytes(self._mock_data) + + @property + def keyfile_data(self) -> bytes: + return bytes(self._mock_data) + + def set_keypair( + self, + keypair: "Keypair", + encrypt: bool = True, + overwrite: bool = False, + password: str = None, + ): + self._mock_keypair = keypair + self._mock_data = serialized_keypair_to_keyfile_data(self._mock_keypair) + + def get_keypair(self, password: str = None) -> "Keypair": + return self._mock_keypair + + def make_dirs(self): + return + + def exists_on_device(self) -> bool: + return True + + def is_readable(self) -> bool: + return True + + def is_writable(self) -> bool: + return True + + def is_encrypted(self) -> bool: + return False + + def encrypt(self, password: str = None): + raise ValueError("Cannot encrypt a mock keyfile") + + def decrypt(self, password: str = None): + return diff --git a/bittensor/_subtensor/subtensor_mock.py b/bittensor/mock/subtensor_mock.py similarity index 87% rename from bittensor/_subtensor/subtensor_mock.py rename to bittensor/mock/subtensor_mock.py index 0b57f6f6c5..e91576cb8c 100644 --- a/bittensor/_subtensor/subtensor_mock.py +++ b/bittensor/mock/subtensor_mock.py @@ -20,24 +20,56 @@ from typing import Any, Dict, List, Optional, Tuple, TypedDict, Union from unittest.mock import MagicMock from dataclasses import dataclass -from abc import ABC, abstractclassmethod +from abc import abstractclassmethod from collections.abc import Mapping -import bittensor -from bittensor.utils import RAOPERTAO, U16_NORMALIZED_FLOAT -from bittensor.utils.registration import POWSolution from hashlib import sha256 +from ..wallet import wallet -from .chain_data import ( +from ..chain_data import ( NeuronInfo, NeuronInfoLite, PrometheusInfo, DelegateInfo, SubnetInfo, - axon_info, + AxonInfo, ) -from .errors import * -from .subtensor_impl import Subtensor, AxonServeCallParams, PrometheusServeCallParams +from ..errors import * +from ..subtensor import subtensor +from ..utils import RAOPERTAO, U16_NORMALIZED_FLOAT +from ..utils.balance import Balance +from ..utils.registration import POWSolution + +from typing import TypedDict + + +# Mock Testing Constant +__GLOBAL_MOCK_STATE__ = {} + + +class AxonServeCallParams(TypedDict): + """ + Axon serve chain call parameters. + """ + + version: int + ip: int + port: int + ip_type: int + netuid: int + + +class PrometheusServeCallParams(TypedDict): + """ + Prometheus serve chain call parameters. + """ + + version: int + ip: int + port: int + ip_type: int + netuid: int + BlockNumber = int @@ -147,7 +179,9 @@ class MockSubtensorState(TypedDict): ] # netuid -> block -> validator_batch_size Active: Dict[int, Dict[BlockNumber, bool]] # (netuid, uid), block -> active Stake: Dict[str, Dict[str, Dict[int, int]]] # (hotkey, coldkey) -> block -> stake + Delegates: Dict[str, Dict[int, float]] # address -> block -> delegate_take + NetworksAdded: Dict[int, Dict[BlockNumber, bool]] # netuid -> block -> added @@ -156,7 +190,7 @@ class MockChainState(TypedDict): SubtensorModule: MockSubtensorState -class MockSubtensor(Subtensor): +class MockSubtensor(subtensor): """ A Mock Subtensor class for running tests. This should mock only methods that make queries to the chain. @@ -170,7 +204,7 @@ class MockSubtensor(Subtensor): @classmethod def reset(cls) -> None: - bittensor.__GLOBAL_MOCK_STATE__.clear() + __GLOBAL_MOCK_STATE__.clear() _ = cls() @@ -178,9 +212,7 @@ def setup(self) -> None: if not hasattr(self, "chain_state") or getattr(self, "chain_state") is None: self.chain_state = { "System": {"Account": {}}, - "Balances": { - "ExistentialDeposit": {0: 500}, - }, + "Balances": {"ExistentialDeposit": {0: 500}}, "SubtensorModule": { "NetworksAdded": {}, "Rho": {}, @@ -230,6 +262,7 @@ def setup(self) -> None: "Delegates": {}, "Axons": {}, "Prometheus": {}, + "SubnetOwner": {}, }, } @@ -239,8 +272,8 @@ def setup(self) -> None: self.chain_endpoint = "mock_endpoint" self.substrate = MagicMock() - def __init__(self) -> None: - self.__dict__ = bittensor.__GLOBAL_MOCK_STATE__ + def __init__(self, *args, **kwargs) -> None: + self.__dict__ = __GLOBAL_MOCK_STATE__ if not hasattr(self, "chain_state") or getattr(self, "chain_state") is None: self.setup() @@ -333,12 +366,7 @@ def set_difficulty(self, netuid: int, difficulty: int) -> None: subtensor_state["Difficulty"][netuid][self.block_number] = difficulty - def _register_neuron( - self, - netuid: int, - hotkey: str, - coldkey: str, - ) -> int: + def _register_neuron(self, netuid: int, hotkey: str, coldkey: str) -> int: subtensor_state = self.chain_state["SubtensorModule"] if netuid not in subtensor_state["NetworksAdded"]: raise Exception("Subnet does not exist") @@ -437,14 +465,12 @@ def _register_neuron( return uid @staticmethod - def _convert_to_balance( - balance: Union["bittensor.Balance", float, int] - ) -> "bittensor.Balance": + def _convert_to_balance(balance: Union["Balance", float, int]) -> "Balance": if isinstance(balance, float): - balance = bittensor.Balance.from_tao(balance) + balance = Balance.from_tao(balance) if isinstance(balance, int): - balance = bittensor.Balance.from_rao(balance) + balance = Balance.from_rao(balance) return balance @@ -453,8 +479,8 @@ def force_register_neuron( netuid: int, hotkey: str, coldkey: str, - stake: Union["bittensor.Balance", float, int] = bittensor.Balance(0), - balance: Union["bittensor.Balance", float, int] = bittensor.Balance(0), + stake: Union["Balance", float, int] = Balance(0), + balance: Union["Balance", float, int] = Balance(0), ) -> int: """ Force register a neuron on the mock chain, returning the UID. @@ -480,9 +506,7 @@ def force_register_neuron( return uid def force_set_balance( - self, - ss58_address: str, - balance: Union["bittensor.Balance", float, int] = bittensor.Balance(0), + self, ss58_address: str, balance: Union["Balance", float, int] = Balance(0) ) -> Tuple[bool, Optional[str]]: """ Returns: @@ -492,11 +516,7 @@ def force_set_balance( if ss58_address not in self.chain_state["System"]["Account"]: self.chain_state["System"]["Account"][ss58_address] = { - "data": { - "free": { - 0: 0, - }, - }, + "data": {"free": {0: 0}} } old_balance = self.get_balance(ss58_address, self.block_number) @@ -652,7 +672,7 @@ def query_constant( if state_at_block is not None: return SimpleNamespace(value=state_at_block) - return state_at_block # Can be None + return state_at_block["data"]["free"] # Can be None else: return None @@ -661,7 +681,7 @@ def get_current_block(self) -> int: # ==== Balance RPC methods ==== - def get_balance(self, address: str, block: int = None) -> "bittensor.Balance": + def get_balance(self, address: str, block: int = None) -> "Balance": if block: if self.block_number < block: raise Exception("Cannot query block in the future") @@ -674,7 +694,7 @@ def get_balance(self, address: str, block: int = None) -> "bittensor.Balance": if address in state: state = state[address] else: - return bittensor.Balance(0) + return Balance(0) # Use block balance_state = state["data"]["free"] @@ -683,13 +703,13 @@ def get_balance(self, address: str, block: int = None) -> "bittensor.Balance": ) # Can be None if state_at_block is not None: bal_as_int = state_at_block - return bittensor.Balance.from_rao(bal_as_int) + return Balance.from_rao(bal_as_int) else: - return bittensor.Balance(0) + return Balance(0) else: - return bittensor.Balance(0) + return Balance(0) - def get_balances(self, block: int = None) -> Dict[str, "bittensor.Balance"]: + def get_balances(self, block: int = None) -> Dict[str, "Balance"]: balances = {} for address in self.chain_state["System"]["Account"]: balances[address] = self.get_balance(address, block) @@ -855,7 +875,7 @@ def _neuron_subnet_exists( ) stake_dict = { - coldkey: bittensor.Balance.from_rao( + coldkey: Balance.from_rao( self._get_most_recent_storage( subtensor_state["Stake"][hotkey][coldkey], block ) @@ -875,12 +895,8 @@ def _neuron_subnet_exists( validator_trust = U16_NORMALIZED_FLOAT(validator_trust) dividends = U16_NORMALIZED_FLOAT(dividends) prometheus_info = PrometheusInfo.fix_decoded_values(prometheus_info) - axon_info_ = axon_info.from_neuron_info( - { - "hotkey": hotkey, - "coldkey": coldkey, - "axon_info": axon_info_, - } + axon_info_ = AxonInfo.from_neuron_info( + {"hotkey": hotkey, "coldkey": coldkey, "axon_info": axon_info_} ) neuron_info = NeuronInfo( @@ -957,15 +973,15 @@ def neurons_lite( # Extrinsics def _do_delegation( self, - wallet: "bittensor.Wallet", + wallet: "wallet", delegate_ss58: str, - amount: "bittensor.Balance", + amount: "Balance", wait_for_inclusion: bool = True, wait_for_finalization: bool = False, ) -> bool: # Check if delegate if not self.is_hotkey_delegate(hotkey_ss58=delegate_ss58): - raise StakeError("Not a delegate") + raise Exception("Not a delegate") # do stake success = self._do_stake( @@ -980,15 +996,15 @@ def _do_delegation( def _do_undelegation( self, - wallet: "bittensor.Wallet", + wallet: "wallet", delegate_ss58: str, - amount: "bittensor.Balance", + amount: "Balance", wait_for_inclusion: bool = True, wait_for_finalization: bool = False, ) -> bool: # Check if delegate if not self.is_hotkey_delegate(hotkey_ss58=delegate_ss58): - raise StakeError("Not a delegate") + raise Exception("Not a delegate") # do unstake self._do_unstake( @@ -1001,7 +1017,7 @@ def _do_undelegation( def _do_nominate( self, - wallet: "bittensor.Wallet", + wallet: "wallet", wait_for_inclusion: bool = True, wait_for_finalization: bool = False, ) -> bool: @@ -1021,18 +1037,15 @@ def _do_nominate( return True def get_transfer_fee( - self, - wallet: "bittensor.Wallet", - dest: str, - value: Union["bittensor.Balance", float, int], - ) -> "bittensor.Balance": - return bittensor.Balance(700) + self, wallet: "wallet", dest: str, value: Union["Balance", float, int] + ) -> "Balance": + return Balance(700) def _do_transfer( self, - wallet: "bittensor.Wallet", + wallet: "wallet", dest: str, - transfer_balance: "bittensor.Balance", + transfer_balance: "Balance", wait_for_inclusion: bool = True, wait_for_finalization: bool = False, ) -> Tuple[bool, Optional[str], Optional[str]]: @@ -1043,7 +1056,7 @@ def _do_transfer( existential_deposit = self.get_existential_deposit() if bal < transfer_balance + existential_deposit + transfer_fee: - raise TransferError("Insufficient balance") + raise Exception("Insufficient balance") # Remove from the free balance self.chain_state["System"]["Account"][wallet.coldkeypub.ss58_address]["data"][ @@ -1052,11 +1065,7 @@ def _do_transfer( # Add to the free balance if dest not in self.chain_state["System"]["Account"]: - self.chain_state["System"]["Account"][dest] = { - "data": { - "free": {}, - } - } + self.chain_state["System"]["Account"][dest] = {"data": {"free": {}}} self.chain_state["System"]["Account"][dest]["data"]["free"][ self.block_number @@ -1067,7 +1076,7 @@ def _do_transfer( def _do_pow_register( self, netuid: int, - wallet: "bittensor.Wallet", + wallet: "wallet", pow_result: "POWSolution", wait_for_inclusion: bool = False, wait_for_finalization: bool = True, @@ -1076,7 +1085,7 @@ def _do_pow_register( subtensor_state = self.chain_state["SubtensorModule"] if netuid not in subtensor_state["NetworksAdded"]: - raise RegistrationError("Subnet does not exist") + raise Exception("Subnet does not exist") self._register_neuron( netuid=netuid, @@ -1089,20 +1098,20 @@ def _do_pow_register( def _do_burned_register( self, netuid: int, - wallet: "bittensor.Wallet", + wallet: "wallet", wait_for_inclusion: bool = False, wait_for_finalization: bool = True, ) -> Tuple[bool, Optional[str]]: subtensor_state = self.chain_state["SubtensorModule"] if netuid not in subtensor_state["NetworksAdded"]: - raise RegistrationError("Subnet does not exist") + raise Exception("Subnet does not exist") bal = self.get_balance(wallet.coldkeypub.ss58_address) burn = self.burn(netuid=netuid) existential_deposit = self.get_existential_deposit() if bal < burn + existential_deposit: - raise RegistrationError("Insufficient funds") + raise Exception("Insufficient funds") self._register_neuron( netuid=netuid, @@ -1119,9 +1128,9 @@ def _do_burned_register( def _do_stake( self, - wallet: "bittensor.Wallet", + wallet: "wallet", hotkey_ss58: str, - amount: "bittensor.Balance", + amount: "Balance", wait_for_inclusion: bool = True, wait_for_finalization: bool = False, ) -> bool: @@ -1132,11 +1141,11 @@ def _do_stake( hotkey_ss58=hotkey_ss58, coldkey_ss58=wallet.coldkeypub.ss58_address ) if curr_stake is None: - curr_stake = bittensor.Balance(0) + curr_stake = Balance(0) existential_deposit = self.get_existential_deposit() if bal < amount + existential_deposit: - raise StakeError("Insufficient funds") + raise Exception("Insufficient funds") stake_state = subtensor_state["Stake"] @@ -1190,9 +1199,9 @@ def _do_stake( def _do_unstake( self, - wallet: "bittensor.Wallet", + wallet: "wallet", hotkey_ss58: str, - amount: "bittensor.Balance", + amount: "Balance", wait_for_inclusion: bool = True, wait_for_finalization: bool = False, ) -> bool: @@ -1203,10 +1212,10 @@ def _do_unstake( hotkey_ss58=hotkey_ss58, coldkey_ss58=wallet.coldkeypub.ss58_address ) if curr_stake is None: - curr_stake = bittensor.Balance(0) + curr_stake = Balance(0) if curr_stake < amount: - raise StakeError("Insufficient funds") + raise Exception("Insufficient funds") stake_state = subtensor_state["Stake"] @@ -1221,9 +1230,7 @@ def _do_unstake( # Add to the free balance if wallet.coldkeypub.ss58_address not in self.chain_state["System"]["Account"]: self.chain_state["System"]["Account"][wallet.coldkeypub.ss58_address] = { - "data": { - "free": {}, - } + "data": {"free": {}} } # Remove from total stake storage @@ -1266,7 +1273,7 @@ def _do_unstake( def get_delegate_by_hotkey( self, hotkey_ss58: str, block: Optional[int] = None - ) -> Optional["bittensor.DelegateInfo"]: + ) -> Optional["DelegateInfo"]: subtensor_state = self.chain_state["SubtensorModule"] if hotkey_ss58 not in subtensor_state["Delegates"]: @@ -1298,10 +1305,8 @@ def get_delegate_by_hotkey( info = DelegateInfo( hotkey_ss58=hotkey_ss58, - total_stake=self.get_total_stake_for_hotkey( - ss58_address=hotkey_ss58, - ) - or bittensor.Balance(0), + total_stake=self.get_total_stake_for_hotkey(ss58_address=hotkey_ss58) + or Balance(0), nominators=nom_result, owner_ss58=self.get_hotkey_owner(hotkey_ss58=hotkey_ss58, block=block), take=0.18, @@ -1311,26 +1316,17 @@ def get_delegate_by_hotkey( if self.neuron_has_validator_permit(uid=uid, netuid=subnet, block=block) ], registrations=[subnet for subnet, _ in registered_subnets], - return_per_1000=bittensor.Balance.from_tao( - 1234567 - ), # Doesn't matter for mock? - total_daily_return=bittensor.Balance.from_tao( - 1234567 - ), # Doesn't matter for mock? + return_per_1000=Balance.from_tao(1234567), # Doesn't matter for mock? + total_daily_return=Balance.from_tao(1234567), # Doesn't matter for mock? ) return info - def get_delegates( - self, block: Optional[int] = None - ) -> List["bittensor.DelegateInfo"]: + def get_delegates(self, block: Optional[int] = None) -> List["DelegateInfo"]: subtensor_state = self.chain_state["SubtensorModule"] delegates_info = [] for hotkey in subtensor_state["Delegates"]: - info = self.get_delegate_by_hotkey( - hotkey_ss58=hotkey, - block=block, - ) + info = self.get_delegate_by_hotkey(hotkey_ss58=hotkey, block=block) if info is not None: delegates_info.append(info) @@ -1338,7 +1334,7 @@ def get_delegates( def get_delegated( self, coldkey_ss58: str, block: Optional[int] = None - ) -> List[Tuple["bittensor.DelegateInfo", "bittensor.Balance"]]: + ) -> List[Tuple["DelegateInfo", "Balance"]]: """Returns the list of delegates that a given coldkey is staked to.""" delegates = self.get_delegates(block=block) @@ -1353,10 +1349,7 @@ def get_all_subnets_info(self, block: Optional[int] = None) -> List[SubnetInfo]: subtensor_state = self.chain_state["SubtensorModule"] result = [] for subnet in subtensor_state["NetworksAdded"]: - info = self.get_subnet_info( - netuid=subnet, - block=block, - ) + info = self.get_subnet_info(netuid=subnet, block=block) if info is not None: result.append(info) @@ -1365,10 +1358,7 @@ def get_all_subnets_info(self, block: Optional[int] = None) -> List[SubnetInfo]: def get_subnet_info( self, netuid: int, block: Optional[int] = None ) -> Optional[SubnetInfo]: - if not self.subnet_exists( - netuid=netuid, - block=block, - ): + if not self.subnet_exists(netuid=netuid, block=block): return None def query_subnet_info(name: str) -> Optional[object]: @@ -1376,79 +1366,35 @@ def query_subnet_info(name: str) -> Optional[object]: info = SubnetInfo( netuid=netuid, - rho=query_subnet_info( - name="Rho", - ), - kappa=query_subnet_info( - name="Kappa", - ), - difficulty=query_subnet_info( - name="Difficulty", - ), - immunity_period=query_subnet_info( - name="ImmunityPeriod", - ), - validator_batch_size=query_subnet_info( - name="ValidatorBatchSize", - ), - validator_sequence_length=query_subnet_info( - name="ValidatorSequenceLength", - ), - validator_epochs_per_reset=query_subnet_info( - name="ValidatorEpochsPerReset", - ), - validator_epoch_length=query_subnet_info( - name="ValidatorEpochLength", - ), - max_allowed_validators=query_subnet_info( - name="MaxAllowedValidators", - ), - min_allowed_weights=query_subnet_info( - name="MinAllowedWeights", - ), - max_weight_limit=query_subnet_info( - name="MaxWeightLimit", - ), - scaling_law_power=query_subnet_info( - name="ScalingLawPower", - ), - synergy_scaling_law_power=query_subnet_info( - name="SynergyScalingLawPower", - ), - subnetwork_n=query_subnet_info( - name="SubnetworkN", - ), - max_n=query_subnet_info( - name="MaxAllowedUids", - ), - blocks_since_epoch=query_subnet_info( - name="BlocksSinceLastStep", - ), - tempo=query_subnet_info( - name="Tempo", - ), - modality=query_subnet_info( - name="NetworkModality", - ), + rho=query_subnet_info(name="Rho"), + kappa=query_subnet_info(name="Kappa"), + difficulty=query_subnet_info(name="Difficulty"), + immunity_period=query_subnet_info(name="ImmunityPeriod"), + max_allowed_validators=query_subnet_info(name="MaxAllowedValidators"), + min_allowed_weights=query_subnet_info(name="MinAllowedWeights"), + max_weight_limit=query_subnet_info(name="MaxWeightLimit"), + scaling_law_power=query_subnet_info(name="ScalingLawPower"), + subnetwork_n=query_subnet_info(name="SubnetworkN"), + max_n=query_subnet_info(name="MaxAllowedUids"), + blocks_since_epoch=query_subnet_info(name="BlocksSinceLastStep"), + tempo=query_subnet_info(name="Tempo"), + modality=query_subnet_info(name="NetworkModality"), connection_requirements={ str(netuid_.value): percentile.value for netuid_, percentile in self.query_map_subtensor( name="NetworkConnect", block=block, params=[netuid] ).records }, - emission_value=query_subnet_info( - name="EmissionValues", - ), - burn=query_subnet_info( - name="Burn", - ), + emission_value=query_subnet_info(name="EmissionValues"), + burn=query_subnet_info(name="Burn"), + owner_ss58=query_subnet_info(name="SubnetOwner"), ) return info def _do_serve_prometheus( self, - wallet: "bittensor.wallet", + wallet: "wallet", call_params: "PrometheusServeCallParams", wait_for_inclusion: bool = False, wait_for_finalization: bool = True, @@ -1457,7 +1403,7 @@ def _do_serve_prometheus( def _do_set_weights( self, - wallet: "bittensor.wallet", + wallet: "wallet", netuid: int, uids: int, vals: List[int], @@ -1469,7 +1415,7 @@ def _do_set_weights( def _do_serve_axon( self, - wallet: "bittensor.wallet", + wallet: "wallet", call_params: "AxonServeCallParams", wait_for_inclusion: bool = False, wait_for_finalization: bool = True, diff --git a/bittensor/mock/wallet_mock.py b/bittensor/mock/wallet_mock.py new file mode 100644 index 0000000000..35179f8c94 --- /dev/null +++ b/bittensor/mock/wallet_mock.py @@ -0,0 +1,127 @@ +# The MIT License (MIT) + +# Copyright © 2021 Yuma Rao +# Copyright © 2022 Opentensor Foundation +# Copyright © 2023 Opentensor Technologies + +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the “Software”), to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of +# the Software. + +# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# 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 os +import bittensor +from typing import Optional +from Crypto.Hash import keccak + +from .keyfile_mock import MockKeyfile + + +class MockWallet(bittensor.wallet): + """ + Mocked Version of the bittensor wallet class, meant to be used for testing + """ + + def __init__(self, **kwargs): + r"""Init bittensor wallet object containing a hot and coldkey. + Args: + _mock (required=True, default=False): + If true creates a mock wallet with random keys. + """ + super().__init__(**kwargs) + # For mocking. + self._is_mock = True + self._mocked_coldkey_keyfile = None + self._mocked_hotkey_keyfile = None + + @property + def hotkey_file(self) -> "bittensor.keyfile": + if self._is_mock: + if self._mocked_hotkey_keyfile == None: + self._mocked_hotkey_keyfile = MockKeyfile(path="MockedHotkey") + return self._mocked_hotkey_keyfile + else: + wallet_path = os.path.expanduser(os.path.join(self.path, self.name)) + hotkey_path = os.path.join(wallet_path, "hotkeys", self.hotkey_str) + return bittensor.keyfile(path=hotkey_path) + + @property + def coldkey_file(self) -> "bittensor.keyfile": + if self._is_mock: + if self._mocked_coldkey_keyfile == None: + self._mocked_coldkey_keyfile = MockKeyfile(path="MockedColdkey") + return self._mocked_coldkey_keyfile + else: + wallet_path = os.path.expanduser(os.path.join(self.path, self.name)) + coldkey_path = os.path.join(wallet_path, "coldkey") + return bittensor.keyfile(path=coldkey_path) + + @property + def coldkeypub_file(self) -> "bittensor.keyfile": + if self._is_mock: + if self._mocked_coldkey_keyfile == None: + self._mocked_coldkey_keyfile = MockKeyfile(path="MockedColdkeyPub") + return self._mocked_coldkey_keyfile + else: + wallet_path = os.path.expanduser(os.path.join(self.path, self.name)) + coldkeypub_path = os.path.join(wallet_path, "coldkeypub.txt") + return bittensor.keyfile(path=coldkeypub_path) + + +def get_mock_wallet( + coldkey: "bittensor.Keypair" = None, hotkey: "bittensor.Keypair" = None +): + wallet = MockWallet(name="mock_wallet", hotkey="mock", path="/tmp/mock_wallet") + + if not coldkey: + coldkey = bittensor.Keypair.create_from_mnemonic( + bittensor.Keypair.generate_mnemonic() + ) + if not hotkey: + hotkey = bittensor.Keypair.create_from_mnemonic( + bittensor.Keypair.generate_mnemonic() + ) + + wallet.set_coldkey(coldkey, encrypt=False, overwrite=True) + wallet.set_coldkeypub(coldkey, encrypt=False, overwrite=True) + wallet.set_hotkey(hotkey, encrypt=False, overwrite=True) + + return wallet + + +def get_mock_keypair(uid: int, test_name: Optional[str] = None) -> bittensor.Keypair: + """ + Returns a mock keypair from a uid and optional test_name. + If test_name is not provided, the uid is the only seed. + If test_name is provided, the uid is hashed with the test_name to create a unique seed for the test. + """ + if test_name is not None: + hashed_test_name: bytes = keccak.new( + digest_bits=256, data=test_name.encode("utf-8") + ).digest() + hashed_test_name_as_int: int = int.from_bytes( + hashed_test_name, byteorder="big", signed=False + ) + uid = uid + hashed_test_name_as_int + + return bittensor.Keypair.create_from_seed( + seed_hex=int.to_bytes(uid, 32, "big", signed=False), + ss58_format=bittensor.__ss58_format__, + ) + + +def get_mock_hotkey(uid: int) -> str: + return get_mock_keypair(uid).ss58_address + + +def get_mock_coldkey(uid: int) -> str: + return get_mock_keypair(uid).ss58_address diff --git a/bittensor/stream.py b/bittensor/stream.py new file mode 100644 index 0000000000..28374e42a0 --- /dev/null +++ b/bittensor/stream.py @@ -0,0 +1,144 @@ +import bittensor + +from starlette.responses import StreamingResponse as _StreamingResponse +from starlette.responses import Response +from starlette.types import Send, Receive, Scope +from typing import Callable, Awaitable, List +from pydantic import BaseModel +from abc import ABC, abstractmethod + + +class BTStreamingResponseModel(BaseModel): + """ + BTStreamingResponseModel is a Pydantic model that encapsulates the token streamer callable for Pydantic validation. + It is used within the StreamingSynapse class to create a BTStreamingResponse object, which is responsible for handling + the streaming of tokens. + + The token streamer is a callable that takes a send function and returns an awaitable. It is responsible for generating + the content of the streaming response, typically by processing tokens and sending them to the client. + + This model ensures that the token streamer conforms to the expected signature and provides a clear interface for + passing the token streamer to the BTStreamingResponse class. + + Attributes: + token_streamer: Callable[[Send], Awaitable[None]] + The token streamer callable, which takes a send function (provided by the ASGI server) and returns an awaitable. + It is responsible for generating the content of the streaming response. + """ + + token_streamer: Callable[[Send], Awaitable[None]] + + +class StreamingSynapse(bittensor.Synapse, ABC): + """ + The StreamingSynapse class is designed to be subclassed for handling streaming responses in the Bittensor network. + It provides abstract methods that must be implemented by the subclass to deserialize, process streaming responses, + and extract JSON data. It also includes a method to create a streaming response object. + """ + + class Config: + validate_assignment = True + + class BTStreamingResponse(_StreamingResponse): + """ + BTStreamingResponse is a specialized subclass of the Starlette StreamingResponse designed to handle the streaming + of tokens within the Bittensor network. It is used internally by the StreamingSynapse class to manage the response + streaming process, including sending headers and calling the token streamer provided by the subclass. + + This class is not intended to be directly instantiated or modified by developers subclassing StreamingSynapse. + Instead, it is used by the create_streaming_response method to create a response object based on the token streamer + provided by the subclass. + """ + + def __init__(self, model: BTStreamingResponseModel, **kwargs): + """ + Initializes the BTStreamingResponse with the given token streamer model. + + Args: + model: A BTStreamingResponseModel instance containing the token streamer callable, which is responsible + for generating the content of the response. + **kwargs: Additional keyword arguments passed to the parent StreamingResponse class. + """ + super().__init__(content=iter(()), **kwargs) + self.token_streamer = model.token_streamer + + async def stream_response(self, send: Send): + """ + Asynchronously streams the response by sending headers and calling the token streamer. + + This method is responsible for initiating the response by sending the appropriate headers, including the + content type for event-streaming. It then calls the token streamer to generate the content and sends the + response body to the client. + + Args: + send: A callable to send the response, provided by the ASGI server. + """ + headers = [(b"content-type", b"text/event-stream")] + self.raw_headers + + await send( + {"type": "http.response.start", "status": 200, "headers": headers} + ) + + await self.token_streamer(send) + + await send({"type": "http.response.body", "body": b"", "more_body": False}) + + async def __call__(self, scope: Scope, receive: Receive, send: Send): + """ + Asynchronously calls the stream_response method, allowing the BTStreamingResponse object to be used as an ASGI + application. + + This method is part of the ASGI interface and is called by the ASGI server to handle the request and send the + response. It delegates to the stream_response method to perform the actual streaming process. + + Args: + scope: The scope of the request, containing information about the client, server, and request itself. + receive: A callable to receive the request, provided by the ASGI server. + send: A callable to send the response, provided by the ASGI server. + """ + await self.stream_response(send) + + @abstractmethod + async def process_streaming_response(self, response: Response): + """ + Abstract method that must be implemented by the subclass. + This method should provide logic to handle the streaming response, such as parsing and accumulating data. + It is called as the response is being streamed from the network, and should be implemented to handle the specific + streaming data format and requirements of the subclass. + + Args: + response: The response object to be processed, typically containing chunks of data. + """ + ... + + @abstractmethod + def extract_response_json(self, response: Response) -> dict: + """ + Abstract method that must be implemented by the subclass. + This method should provide logic to extract JSON data from the response, including headers and content. + It is called after the response has been processed and is responsible for retrieving structured data + that can be used by the application. + + Args: + response: The response object from which to extract JSON data. + """ + ... + + def create_streaming_response( + self, token_streamer: Callable[[Send], Awaitable[None]] + ) -> BTStreamingResponse: + """ + Creates a streaming response using the provided token streamer. + This method can be used by the subclass to create a response object that can be sent back to the client. + The token streamer should be implemented to generate the content of the response according to the specific + requirements of the subclass. + + Args: + token_streamer: A callable that takes a send function and returns an awaitable. It's responsible for generating the content of the response. + + Returns: + BTStreamingResponse: The streaming response object, ready to be sent to the client. + """ + model_instance = BTStreamingResponseModel(token_streamer=token_streamer) + + return self.BTStreamingResponse(model_instance) diff --git a/bittensor/_subtensor/subtensor_impl.py b/bittensor/subtensor.py similarity index 57% rename from bittensor/_subtensor/subtensor_impl.py rename to bittensor/subtensor.py index ae1ab1ad2b..99b15d5060 100644 --- a/bittensor/_subtensor/subtensor_impl.py +++ b/bittensor/subtensor.py @@ -16,17 +16,19 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. -# Imports +import os +import copy import torch +import argparse import bittensor import scalecodec + from retry import retry -from typing import List, Dict, Union, Optional, Tuple +from loguru import logger +from typing import List, Dict, Union, Optional, Tuple, TypedDict, Any from substrateinterface.base import QueryMapResult, SubstrateInterface - -from bittensor.utils.balance import Balance -from bittensor.utils import U16_NORMALIZED_FLOAT, U64_MAX, RAOPERTAO, U16_MAX -from bittensor.utils.registration import POWSolution +from scalecodec.base import RuntimeConfiguration +from scalecodec.type_registry import load_type_registry_preset # Local imports. from .chain_data import ( @@ -34,16 +36,28 @@ DelegateInfo, PrometheusInfo, SubnetInfo, + SubnetHyperparameters, + StakeInfo, NeuronInfoLite, - axon_info, + AxonInfo, ProposalVoteData, ProposalCallData, + IPInfo, + custom_rpc_type_registry, ) from .errors import * +from .extrinsics.network import ( + register_subnetwork_extrinsic, + set_hyperparameter_extrinsic, +) from .extrinsics.staking import add_stake_extrinsic, add_stake_multiple_extrinsic from .extrinsics.unstaking import unstake_extrinsic, unstake_multiple_extrinsic from .extrinsics.serving import serve_extrinsic, serve_axon_extrinsic -from .extrinsics.registration import register_extrinsic, burned_register_extrinsic +from .extrinsics.registration import ( + register_extrinsic, + burned_register_extrinsic, + run_faucet_extrinsic, +) from .extrinsics.transfer import transfer_extrinsic from .extrinsics.set_weights import set_weights_extrinsic from .extrinsics.prometheus import prometheus_extrinsic @@ -57,50 +71,226 @@ leave_senate_extrinsic, vote_senate_extrinsic, ) +from .extrinsics.root import root_register_extrinsic, set_root_weights_extrinsic from .types import AxonServeCallParams, PrometheusServeCallParams - -# Logging -from loguru import logger +from .utils import U16_NORMALIZED_FLOAT, ss58_to_vec_u8 +from .utils.balance import Balance +from .utils.registration import POWSolution logger = logger.opt(colors=True) -class Subtensor: - """ - Handles interactions with the subtensor chain. +class ParamWithTypes(TypedDict): + name: str # Name of the parameter. + type: str # ScaleType string of the parameter. + + +class subtensor: + """Factory Class for bittensor.subtensor + + The Subtensor class handles interactions with the substrate subtensor chain. + By default, the Subtensor class connects to the Finney which serves as the main bittensor network. """ + @staticmethod + def config() -> "bittensor.config": + parser = argparse.ArgumentParser() + subtensor.add_args(parser) + return bittensor.config(parser, args=[]) + + @classmethod + def help(cls): + """Print help to stdout""" + parser = argparse.ArgumentParser() + cls.add_args(parser) + print(cls.__new__.__doc__) + parser.print_help() + + @classmethod + def add_args(cls, parser: argparse.ArgumentParser, prefix: str = None): + prefix_str = "" if prefix == None else prefix + "." + try: + default_network = os.getenv("BT_SUBTENSOR_NETWORK") or "finney" + default_chain_endpoint = ( + os.getenv("BT_SUBTENSOR_CHAIN_ENDPOINT") + or bittensor.__finney_entrypoint__ + ) + parser.add_argument( + "--" + prefix_str + "subtensor.network", + default=default_network, + type=str, + help="""The subtensor network flag. The likely choices are: + -- finney (main network) + -- local (local running network) + If this option is set it overloads subtensor.chain_endpoint with + an entry point node from that network. + """, + ) + parser.add_argument( + "--" + prefix_str + "subtensor.chain_endpoint", + default=default_chain_endpoint, + type=str, + help="""The subtensor endpoint flag. If set, overrides the --network flag. + """, + ) + parser.add_argument( + "--" + prefix_str + "subtensor._mock", + default=False, + type=bool, + help="""If true, uses a mocked connection to the chain. + """, + ) + + except argparse.ArgumentError: + # re-parsing arguments. + pass + + @staticmethod + def determine_chain_endpoint_and_network(network: str): + """Determines the chain endpoint and network from the passed network or chain_endpoint. + Args: + network (str): The network flag. The likely choices are: + -- finney (main network) + -- local (local running network) + -- test (test network) + chain_endpoint (str): The chain endpoint flag. If set, overrides the network argument. + Returns: + network (str): The network flag. The likely choices are: + chain_endpoint (str): The chain endpoint flag. If set, overrides the network argument. + """ + if network == None: + return None, None + if network in ["finney", "local", "test"]: + if network == "finney": + # Kiru Finney stagin network. + return network, bittensor.__finney_entrypoint__ + elif network == "local": + return network, bittensor.__local_entrypoint__ + elif network == "test": + return network, bittensor.__finney_test_entrypoint__ + else: + if ( + network == bittensor.__finney_entrypoint__ + or "entrypoint-finney.opentensor.ai" in network + ): + return "finney", bittensor.__finney_entrypoint__ + elif ( + network == bittensor.__finney_test_entrypoint__ + or "test.finney.opentensor.ai" in network + ): + return "test", bittensor.__finney_test_entrypoint__ + elif "127.0.0.1" in network or "localhost" in network: + return "local", network + else: + return "unknown", network + + @staticmethod + def setup_config(network: str, config: bittensor.config): + if network != None: + ( + evaluated_network, + evaluated_endpoint, + ) = subtensor.determine_chain_endpoint_and_network(network) + else: + if config.get("__is_set", {}).get("subtensor.chain_endpoint"): + ( + evaluated_network, + evaluated_endpoint, + ) = subtensor.determine_chain_endpoint_and_network( + config.subtensor.chain_endpoint + ) + + elif config.get("__is_set", {}).get("subtensor.network"): + ( + evaluated_network, + evaluated_endpoint, + ) = subtensor.determine_chain_endpoint_and_network( + config.subtensor.network + ) + + elif config.subtensor.get("chain_endpoint"): + ( + evaluated_network, + evaluated_endpoint, + ) = subtensor.determine_chain_endpoint_and_network( + config.subtensor.chain_endpoint + ) + + elif config.subtensor.get("network"): + ( + evaluated_network, + evaluated_endpoint, + ) = subtensor.determine_chain_endpoint_and_network( + config.subtensor.network + ) + + else: + ( + evaluated_network, + evaluated_endpoint, + ) = subtensor.determine_chain_endpoint_and_network( + bittensor.defaults.subtensor.network + ) + + return ( + bittensor.utils.networking.get_formatted_ws_endpoint_url( + evaluated_endpoint + ), + evaluated_network, + ) + def __init__( self, - substrate: "SubstrateInterface", - network: str, - chain_endpoint: str, - ): + network: str = None, + config: "bittensor.config" = None, + _mock: bool = False, + ) -> None: r"""Initializes a subtensor chain interface. Args: - substrate (:obj:`SubstrateInterface`, `required`): - substrate websocket client. - network (default='local', type=str) + config (:obj:`bittensor.config`, `optional`): + bittensor.subtensor.config() + network (default='local or ws://127.0.0.1:9946', type=str) The subtensor network flag. The likely choices are: -- local (local running network) - -- nobunaga (staging network) -- finney (main network) - If this option is set it overloads subtensor.chain_endpoint with - an entry point node from that network. - chain_endpoint (default=None, type=str) - The subtensor endpoint flag. If set, overrides the network argument. + or subtensor endpoint flag. If set, overrides the network argument. """ - self.network = network - self.chain_endpoint = chain_endpoint - self.substrate = substrate + + # Determine config.subtensor.chain_endpoint and config.subtensor.network config. + # If chain_endpoint is set, we override the network flag, otherwise, the chain_endpoint is assigned by the network. + # Argument importance: network > chain_endpoint > config.subtensor.chain_endpoint > config.subtensor.network + if config == None: + config = subtensor.config() + self.config = copy.deepcopy(config) + + # Setup config.subtensor.network and config.subtensor.chain_endpoint + self.chain_endpoint, self.network = subtensor.setup_config(network, config) + + # Returns a mocked connection with a background chain connection. + self.config.subtensor._mock = ( + _mock + if _mock != None + else self.config.subtensor.get("_mock", bittensor.defaults.subtensor._mock) + ) + if self.config.subtensor._mock: + config.subtensor._mock = True + return bittensor.subtensor_mock.MockSubtensor() + + # Set up params. + self.substrate = SubstrateInterface( + ss58_format=bittensor.__ss58_format__, + use_remote_preset=True, + url=self.chain_endpoint, + type_registry=bittensor.__type_registry__, + ) def __str__(self) -> str: if self.network == self.chain_endpoint: # Connecting to chain endpoint without network known. - return "Subtensor({})".format(self.chain_endpoint) + return "subtensor({})".format(self.chain_endpoint) else: # Connecting to network with endpoint known. - return "Subtensor({}, {})".format(self.network, self.chain_endpoint) + return "subtensor({}, {})".format(self.network, self.chain_endpoint) def __repr__(self) -> str: return self.__str__() @@ -110,7 +300,7 @@ def __repr__(self) -> str: ##################### def nominate( self, - wallet: "bittensor.Wallet", + wallet: "bittensor.wallet", wait_for_finalization: bool = False, wait_for_inclusion: bool = True, ) -> bool: @@ -198,42 +388,46 @@ def _do_set_weights( wait_for_inclusion: bool = False, wait_for_finalization: bool = True, ) -> Tuple[bool, Optional[str]]: # (success, error_message) - with self.substrate as substrate: - call = substrate.compose_call( - call_module="SubtensorModule", - call_function="set_weights", - call_params={ - "dests": uids, - "weights": vals, - "netuid": netuid, - "version_key": version_key, - }, - ) - # Period dictates how long the extrinsic will stay as part of waiting pool - extrinsic = substrate.create_signed_extrinsic( - call=call, keypair=wallet.hotkey, era={"period": 100} - ) - response = substrate.submit_extrinsic( - extrinsic, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) - # We only wait here if we expect finalization. - if not wait_for_finalization and not wait_for_inclusion: - return True, None + @retry(delay=2, tries=3, backoff=2, max_delay=4) + def make_substrate_call_with_retry(): + with self.substrate as substrate: + call = substrate.compose_call( + call_module="SubtensorModule", + call_function="set_weights", + call_params={ + "dests": uids, + "weights": vals, + "netuid": netuid, + "version_key": version_key, + }, + ) + # Period dictates how long the extrinsic will stay as part of waiting pool + extrinsic = substrate.create_signed_extrinsic( + call=call, keypair=wallet.hotkey, era={"period": 100} + ) + response = substrate.submit_extrinsic( + extrinsic, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + # We only wait here if we expect finalization. + if not wait_for_finalization and not wait_for_inclusion: + return True, None - response.process_events() - if response.is_success: - return True, None - else: - return False, response.error_message + response.process_events() + if response.is_success: + return True, None + else: + return False, response.error_message + + return make_substrate_call_with_retry() ###################### #### Registration #### ###################### def register( self, - wallet: "bittensor.Wallet", + wallet: "bittensor.wallet", netuid: int, wait_for_inclusion: bool = False, wait_for_finalization: bool = True, @@ -265,9 +459,41 @@ def register( log_verbose=log_verbose, ) + def run_faucet( + self, + wallet: "bittensor.wallet", + wait_for_inclusion: bool = False, + wait_for_finalization: bool = True, + prompt: bool = False, + max_allowed_attempts: int = 3, + output_in_place: bool = True, + cuda: bool = False, + dev_id: Union[List[int], int] = 0, + TPB: int = 256, + num_processes: Optional[int] = None, + update_interval: Optional[int] = None, + log_verbose: bool = False, + ) -> bool: + """Registers the wallet to chain.""" + return run_faucet_extrinsic( + subtensor=self, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + prompt=prompt, + max_allowed_attempts=max_allowed_attempts, + output_in_place=output_in_place, + cuda=cuda, + dev_id=dev_id, + TPB=TPB, + num_processes=num_processes, + update_interval=update_interval, + log_verbose=log_verbose, + ) + def burned_register( self, - wallet: "bittensor.Wallet", + wallet: "bittensor.wallet", netuid: int, wait_for_inclusion: bool = False, wait_for_finalization: bool = True, @@ -286,7 +512,7 @@ def burned_register( def _do_pow_register( self, netuid: int, - wallet: "bittensor.Wallet", + wallet: "bittensor.wallet", pow_result: POWSolution, wait_for_inclusion: bool = False, wait_for_finalization: bool = True, @@ -294,7 +520,7 @@ def _do_pow_register( """Sends a (POW) register extrinsic to the chain. Args: netuid (int): the subnet to register on. - wallet (bittensor.Wallet): the wallet to register. + wallet (bittensor.wallet): the wallet to register. pow_result (POWSolution): the pow result to register. wait_for_inclusion (bool): if true, waits for the extrinsic to be included in a block. wait_for_finalization (bool): if true, waits for the extrinsic to be finalized. @@ -302,81 +528,87 @@ def _do_pow_register( success (bool): True if the extrinsic was included in a block. error (Optional[str]): None on success or not waiting for inclusion/finalization, otherwise the error message. """ - with self.substrate as substrate: - # create extrinsic call - call = substrate.compose_call( - call_module="SubtensorModule", - call_function="register", - call_params={ - "netuid": netuid, - "block_number": pow_result.block_number, - "nonce": pow_result.nonce, - "work": [int(byte_) for byte_ in pow_result.seal], - "hotkey": wallet.hotkey.ss58_address, - "coldkey": wallet.coldkeypub.ss58_address, - }, - ) - extrinsic = substrate.create_signed_extrinsic( - call=call, keypair=wallet.hotkey - ) - response = substrate.submit_extrinsic( - extrinsic, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) - # We only wait here if we expect finalization. - if not wait_for_finalization and not wait_for_inclusion: - bittensor.__console__.print( - ":white_heavy_check_mark: [green]Sent[/green]" + @retry(delay=2, tries=3, backoff=2, max_delay=4) + def make_substrate_call_with_retry(): + with self.substrate as substrate: + # create extrinsic call + call = substrate.compose_call( + call_module="SubtensorModule", + call_function="register", + call_params={ + "netuid": netuid, + "block_number": pow_result.block_number, + "nonce": pow_result.nonce, + "work": [int(byte_) for byte_ in pow_result.seal], + "hotkey": wallet.hotkey.ss58_address, + "coldkey": wallet.coldkeypub.ss58_address, + }, + ) + extrinsic = substrate.create_signed_extrinsic( + call=call, keypair=wallet.hotkey + ) + response = substrate.submit_extrinsic( + extrinsic, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, ) - return True, None - # process if registration successful, try again if pow is still valid - response.process_events() - if not response.is_success: - return False, response.error_message - # Successful registration - else: - return True, None + # We only wait here if we expect finalization. + if not wait_for_finalization and not wait_for_inclusion: + return True, None + + # process if registration successful, try again if pow is still valid + response.process_events() + if not response.is_success: + return False, response.error_message + # Successful registration + else: + return True, None + + return make_substrate_call_with_retry() def _do_burned_register( self, netuid: int, - wallet: "bittensor.Wallet", + wallet: "bittensor.wallet", wait_for_inclusion: bool = False, wait_for_finalization: bool = True, ) -> Tuple[bool, Optional[str]]: - with self.substrate as substrate: - # create extrinsic call - call = substrate.compose_call( - call_module="SubtensorModule", - call_function="burned_register", - call_params={"netuid": netuid, "hotkey": wallet.hotkey.ss58_address}, - ) - extrinsic = substrate.create_signed_extrinsic( - call=call, keypair=wallet.coldkey - ) - response = substrate.submit_extrinsic( - extrinsic, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) - - # We only wait here if we expect finalization. - if not wait_for_finalization and not wait_for_inclusion: - bittensor.__console__.print( - ":white_heavy_check_mark: [green]Sent[/green]" + @retry(delay=2, tries=3, backoff=2, max_delay=4) + def make_substrate_call_with_retry(): + with self.substrate as substrate: + # create extrinsic call + call = substrate.compose_call( + call_module="SubtensorModule", + call_function="burned_register", + call_params={ + "netuid": netuid, + "hotkey": wallet.hotkey.ss58_address, + }, + ) + extrinsic = substrate.create_signed_extrinsic( + call=call, keypair=wallet.coldkey + ) + response = substrate.submit_extrinsic( + extrinsic, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, ) - return True - # process if registration successful, try again if pow is still valid - response.process_events() - if not response.is_success: - return False, response.error_message - # Successful registration - else: - return True, None + # We only wait here if we expect finalization. + if not wait_for_finalization and not wait_for_inclusion: + return True + + # process if registration successful, try again if pow is still valid + response.process_events() + if not response.is_success: + return False, response.error_message + # Successful registration + else: + return True, None + + return make_substrate_call_with_retry() ################## #### Transfer #### @@ -402,15 +634,12 @@ def transfer( ) def get_transfer_fee( - self, - wallet: "bittensor.Wallet", - dest: str, - value: Union[Balance, float, int], + self, wallet: "bittensor.wallet", dest: str, value: Union[Balance, float, int] ) -> Balance: if isinstance(value, float): - transfer_balance = bittensor.Balance.from_tao(value) + transfer_balance = Balance.from_tao(value) elif isinstance(value, int): - transfer_balance = bittensor.Balance.from_rao(value) + transfer_balance = Balance.from_rao(value) with self.substrate as substrate: call = substrate.compose_call( @@ -429,11 +658,9 @@ def get_transfer_fee( e ) ) - payment_info = { - "partialFee": 2e7, # assume 0.02 Tao - } + payment_info = {"partialFee": 2e7} # assume 0.02 Tao - fee = bittensor.Balance.from_rao(payment_info["partialFee"]) + fee = Balance.from_rao(payment_info["partialFee"]) return fee def _do_transfer( @@ -448,7 +675,7 @@ def _do_transfer( Args: wallet (:obj:`bittensor.wallet`): Wallet object. dest (:obj:`str`): Destination public key address. - transfer_balance (:obj:`bittensor.Balance`): Amount to transfer. + transfer_balance (:obj:`Balance`): Amount to transfer. wait_for_inclusion (:obj:`bool`): If true, waits for inclusion. wait_for_finalization (:obj:`bool`): If true, waits for finalization. Returns: @@ -457,44 +684,41 @@ def _do_transfer( (On success and if wait_for_ finalization/inclusion is True) error (:obj:`str`): Error message if transfer failed. """ - with self.substrate as substrate: - call = substrate.compose_call( - call_module="Balances", - call_function="transfer", - call_params={"dest": dest, "value": transfer_balance.rao}, - ) - extrinsic = substrate.create_signed_extrinsic( - call=call, keypair=wallet.coldkey - ) - response = substrate.submit_extrinsic( - extrinsic, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) - # We only wait here if we expect finalization. - if not wait_for_finalization and not wait_for_inclusion: - bittensor.__console__.print( - ":white_heavy_check_mark: [green]Sent[/green]" + + @retry(delay=2, tries=3, backoff=2, max_delay=4) + def make_substrate_call_with_retry(): + with self.substrate as substrate: + call = substrate.compose_call( + call_module="Balances", + call_function="transfer", + call_params={"dest": dest, "value": transfer_balance.rao}, ) - return True, None, None + extrinsic = substrate.create_signed_extrinsic( + call=call, keypair=wallet.coldkey + ) + response = substrate.submit_extrinsic( + extrinsic, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + # We only wait here if we expect finalization. + if not wait_for_finalization and not wait_for_inclusion: + return True, None, None - # Otherwise continue with finalization. - response.process_events() - if response.is_success: - block_hash = response.block_hash - return True, block_hash, None - else: - return False, None, response.error_message + # Otherwise continue with finalization. + response.process_events() + if response.is_success: + block_hash = response.block_hash + return True, block_hash, None + else: + return False, None, response.error_message - def get_existential_deposit( - self, - block: Optional[int] = None, - ) -> Optional[Balance]: + return make_substrate_call_with_retry() + + def get_existential_deposit(self, block: Optional[int] = None) -> Optional[Balance]: """Returns the existential deposit for the chain.""" result = self.query_constant( - module_name="Balances", - constant_name="ExistentialDeposit", - block=block, + module_name="Balances", constant_name="ExistentialDeposit", block=block ) if result is None: @@ -502,6 +726,45 @@ def get_existential_deposit( return Balance.from_rao(result.value) + ################# + #### Network #### + ################# + def register_subnetwork( + self, + wallet: "bittensor.wallet", + wait_for_inclusion: bool = False, + wait_for_finalization=True, + prompt: bool = False, + ) -> bool: + return register_subnetwork_extrinsic( + self, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + prompt=prompt, + ) + + def set_hyperparameter( + self, + wallet: "bittensor.wallet", + netuid: int, + parameter: str, + value, + wait_for_inclusion: bool = False, + wait_for_finalization=True, + prompt: bool = False, + ) -> bool: + return set_hyperparameter_extrinsic( + self, + wallet=wallet, + netuid=netuid, + parameter=parameter, + value=value, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + prompt=prompt, + ) + ################# #### Serving #### ################# @@ -535,13 +798,12 @@ def serve_axon( self, netuid: int, axon: "bittensor.Axon", - use_upnpc: bool = False, wait_for_inclusion: bool = False, wait_for_finalization: bool = True, prompt: bool = False, ) -> bool: return serve_axon_extrinsic( - self, netuid, axon, use_upnpc, wait_for_inclusion, wait_for_finalization + self, netuid, axon, wait_for_inclusion, wait_for_finalization ) def _do_serve_axon( @@ -551,28 +813,32 @@ def _do_serve_axon( wait_for_inclusion: bool = False, wait_for_finalization: bool = True, ) -> Tuple[bool, Optional[str]]: - with self.substrate as substrate: - call = substrate.compose_call( - call_module="SubtensorModule", - call_function="serve_axon", - call_params=call_params, - ) - extrinsic = substrate.create_signed_extrinsic( - call=call, keypair=wallet.hotkey - ) - response = substrate.submit_extrinsic( - extrinsic, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) - if wait_for_inclusion or wait_for_finalization: - response.process_events() - if response.is_success: - return True, None + @retry(delay=2, tries=3, backoff=2, max_delay=4) + def make_substrate_call_with_retry(): + with self.substrate as substrate: + call = substrate.compose_call( + call_module="SubtensorModule", + call_function="serve_axon", + call_params=call_params, + ) + extrinsic = substrate.create_signed_extrinsic( + call=call, keypair=wallet.hotkey + ) + response = substrate.submit_extrinsic( + extrinsic, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + if wait_for_inclusion or wait_for_finalization: + response.process_events() + if response.is_success: + return True, None + else: + return False, response.error_message else: - return False, response.error_message - else: - return True, None + return True, None + + return make_substrate_call_with_retry() def serve_prometheus( self, @@ -609,28 +875,86 @@ def _do_serve_prometheus( success (:obj:`bool`): True if serve prometheus was successful. error (:obj:`Optional[str]`): Error message if serve prometheus failed, None otherwise. """ - with self.substrate as substrate: - call = substrate.compose_call( - call_module="SubtensorModule", - call_function="serve_prometheus", - call_params=call_params, - ) - extrinsic = substrate.create_signed_extrinsic( - call=call, keypair=wallet.hotkey - ) - response = substrate.submit_extrinsic( - extrinsic, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) - if wait_for_inclusion or wait_for_finalization: - response.process_events() - if response.is_success: + + @retry(delay=2, tries=3, backoff=2, max_delay=4) + def make_substrate_call_with_retry(): + with self.substrate as substrate: + call = substrate.compose_call( + call_module="SubtensorModule", + call_function="serve_prometheus", + call_params=call_params, + ) + extrinsic = substrate.create_signed_extrinsic( + call=call, keypair=wallet.hotkey + ) + response = substrate.submit_extrinsic( + extrinsic, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + if wait_for_inclusion or wait_for_finalization: + response.process_events() + if response.is_success: + return True, None + else: + return False, response.error_message + else: return True, None + + return make_substrate_call_with_retry() + + def _do_associate_ips( + self, + wallet: "bittensor.wallet", + ip_info_list: List[IPInfo], + netuid: int, + wait_for_inclusion: bool = False, + wait_for_finalization: bool = True, + ) -> Tuple[bool, Optional[str]]: + """ + Sends an associate IPs extrinsic to the chain. + + Args: + wallet (:obj:`bittensor.wallet`): Wallet object. + ip_info_list (:obj:`List[IPInfo]`): List of IPInfo objects. + netuid (:obj:`int`): Netuid to associate IPs to. + wait_for_inclusion (:obj:`bool`): If true, waits for inclusion. + wait_for_finalization (:obj:`bool`): If true, waits for finalization. + + Returns: + success (:obj:`bool`): True if associate IPs was successful. + error (:obj:`Optional[str]`): Error message if associate IPs failed, None otherwise. + """ + + @retry(delay=2, tries=3, backoff=2, max_delay=4) + def make_substrate_call_with_retry(): + with self.substrate as substrate: + call = substrate.compose_call( + call_module="SubtensorModule", + call_function="associate_ips", + call_params={ + "ip_info_list": [ip_info.encode() for ip_info in ip_info_list], + "netuid": netuid, + }, + ) + extrinsic = substrate.create_signed_extrinsic( + call=call, keypair=wallet.hotkey + ) + response = substrate.submit_extrinsic( + extrinsic, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + if wait_for_inclusion or wait_for_finalization: + response.process_events() + if response.is_success: + return True, None + else: + return False, response.error_message else: - return False, response.error_message - else: - return True, None + return True, None + + return make_substrate_call_with_retry() ################# #### Staking #### @@ -657,7 +981,7 @@ def add_stake( def add_stake_multiple( self, - wallet: "bittensor.Wallet", + wallet: "bittensor.wallet", hotkey_ss58s: List[str], amounts: List[Union[Balance, float]] = None, wait_for_inclusion: bool = True, @@ -677,7 +1001,7 @@ def add_stake_multiple( def _do_stake( self, - wallet: "bittensor.Wallet", + wallet: "bittensor.wallet", hotkey_ss58: str, amount: Balance, wait_for_inclusion: bool = True, @@ -685,9 +1009,9 @@ def _do_stake( ) -> bool: """Sends a stake extrinsic to the chain. Args: - wallet (:obj:`bittensor.Wallet`): Wallet object that can sign the extrinsic. + wallet (:obj:`bittensor.wallet`): Wallet object that can sign the extrinsic. hotkey_ss58 (:obj:`str`): Hotkey ss58 address to stake to. - amount (:obj:`bittensor.Balance`): Amount to stake. + amount (:obj:`Balance`): Amount to stake. wait_for_inclusion (:obj:`bool`): If true, waits for inclusion before returning. wait_for_finalization (:obj:`bool`): If true, waits for finalization before returning. Returns: @@ -695,29 +1019,34 @@ def _do_stake( Raises: StakeError: If the extrinsic failed. """ - with self.substrate as substrate: - call = substrate.compose_call( - call_module="SubtensorModule", - call_function="add_stake", - call_params={"hotkey": hotkey_ss58, "amount_staked": amount.rao}, - ) - extrinsic = substrate.create_signed_extrinsic( - call=call, keypair=wallet.coldkey - ) - response = substrate.submit_extrinsic( - extrinsic, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) - # We only wait here if we expect finalization. - if not wait_for_finalization and not wait_for_inclusion: - return True - response.process_events() - if response.is_success: - return True - else: - raise StakeError(response.error_message) + @retry(delay=2, tries=3, backoff=2, max_delay=4) + def make_substrate_call_with_retry(): + with self.substrate as substrate: + call = substrate.compose_call( + call_module="SubtensorModule", + call_function="add_stake", + call_params={"hotkey": hotkey_ss58, "amount_staked": amount.rao}, + ) + extrinsic = substrate.create_signed_extrinsic( + call=call, keypair=wallet.coldkey + ) + response = substrate.submit_extrinsic( + extrinsic, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + # We only wait here if we expect finalization. + if not wait_for_finalization and not wait_for_inclusion: + return True + + response.process_events() + if response.is_success: + return True + else: + raise StakeError(response.error_message) + + return make_substrate_call_with_retry() ################### #### Unstaking #### @@ -764,7 +1093,7 @@ def unstake( def _do_unstake( self, - wallet: "bittensor.Wallet", + wallet: "bittensor.wallet", hotkey_ss58: str, amount: Balance, wait_for_inclusion: bool = True, @@ -772,9 +1101,9 @@ def _do_unstake( ) -> bool: """Sends an unstake extrinsic to the chain. Args: - wallet (:obj:`bittensor.Wallet`): Wallet object that can sign the extrinsic. + wallet (:obj:`bittensor.wallet`): Wallet object that can sign the extrinsic. hotkey_ss58 (:obj:`str`): Hotkey ss58 address to unstake from. - amount (:obj:`bittensor.Balance`): Amount to unstake. + amount (:obj:`Balance`): Amount to unstake. wait_for_inclusion (:obj:`bool`): If true, waits for inclusion before returning. wait_for_finalization (:obj:`bool`): If true, waits for finalization before returning. Returns: @@ -782,29 +1111,34 @@ def _do_unstake( Raises: StakeError: If the extrinsic failed. """ - with self.substrate as substrate: - call = substrate.compose_call( - call_module="SubtensorModule", - call_function="remove_stake", - call_params={"hotkey": hotkey_ss58, "amount_unstaked": amount.rao}, - ) - extrinsic = substrate.create_signed_extrinsic( - call=call, keypair=wallet.coldkey - ) - response = substrate.submit_extrinsic( - extrinsic, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) - # We only wait here if we expect finalization. - if not wait_for_finalization and not wait_for_inclusion: - return True - response.process_events() - if response.is_success: - return True - else: - raise StakeError(response.error_message) + @retry(delay=2, tries=3, backoff=2, max_delay=4) + def make_substrate_call_with_retry(): + with self.substrate as substrate: + call = substrate.compose_call( + call_module="SubtensorModule", + call_function="remove_stake", + call_params={"hotkey": hotkey_ss58, "amount_unstaked": amount.rao}, + ) + extrinsic = substrate.create_signed_extrinsic( + call=call, keypair=wallet.coldkey + ) + response = substrate.submit_extrinsic( + extrinsic, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + # We only wait here if we expect finalization. + if not wait_for_finalization and not wait_for_inclusion: + return True + + response.process_events() + if response.is_success: + return True + else: + raise StakeError(response.error_message) + + return make_substrate_call_with_retry() ################ #### Senate #### @@ -853,11 +1187,7 @@ def vote_senate( prompt, ) - def is_senate_member( - self, - hotkey_ss58: str, - block: Optional[int] = None, - ) -> bool: + def is_senate_member(self, hotkey_ss58: str, block: Optional[int] = None) -> bool: senate_members = self.query_module( module="SenateMembers", name="Members", block=block ).serialize() @@ -873,18 +1203,13 @@ def get_vote_data( get_proposal_vote_data = get_vote_data - def get_senate_members( - self, - block: Optional[int] = None, - ) -> Optional[List[str]]: + def get_senate_members(self, block: Optional[int] = None) -> Optional[List[str]]: senate_members = self.query_module("SenateMembers", "Members", block=block) return senate_members.serialize() if senate_members != None else None def get_proposal_call_data( - self, - proposal_hash: str, - block: Optional[int] = None, + self, proposal_hash: str, block: Optional[int] = None ) -> Optional["bittensor.ProposalCallData"]: proposal_data = self.query_module( module="Triumvirate", name="ProposalOf", block=block, params=[proposal_hash] @@ -892,10 +1217,7 @@ def get_proposal_call_data( return proposal_data.serialize() if proposal_data != None else None - def get_proposal_hashes( - self, - block: Optional[int] = None, - ) -> Optional[List[str]]: + def get_proposal_hashes(self, block: Optional[int] = None) -> Optional[List[str]]: proposal_hashes = self.query_module( module="Triumvirate", name="Proposals", block=block ) @@ -903,8 +1225,7 @@ def get_proposal_hashes( return proposal_hashes.serialize() if proposal_hashes != None else None def get_proposals( - self, - block: Optional[int] = None, + self, block: Optional[int] = None ) -> Optional[ Dict[str, Tuple["bittensor.ProposalCallData", "bittensor.ProposalVoteData"]] ]: @@ -919,6 +1240,86 @@ def get_proposals( return proposals + ############## + #### Root #### + ############## + + def root_register( + self, + wallet: "bittensor.wallet", + wait_for_inclusion: bool = False, + wait_for_finalization: bool = True, + prompt: bool = False, + ) -> bool: + """Registers the wallet to root network.""" + return root_register_extrinsic( + subtensor=self, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + prompt=prompt, + ) + + def _do_root_register( + self, + wallet: "bittensor.wallet", + wait_for_inclusion: bool = False, + wait_for_finalization: bool = True, + ) -> Tuple[bool, Optional[str]]: + @retry(delay=2, tries=3, backoff=2, max_delay=4) + def make_substrate_call_with_retry(): + with self.substrate as substrate: + # create extrinsic call + call = substrate.compose_call( + call_module="SubtensorModule", + call_function="root_register", + call_params={"hotkey": wallet.hotkey.ss58_address}, + ) + extrinsic = substrate.create_signed_extrinsic( + call=call, keypair=wallet.coldkey + ) + response = substrate.submit_extrinsic( + extrinsic, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + + # We only wait here if we expect finalization. + if not wait_for_finalization and not wait_for_inclusion: + return True + + # process if registration successful, try again if pow is still valid + response.process_events() + if not response.is_success: + return False, response.error_message + # Successful registration + else: + return True, None + + return make_substrate_call_with_retry() + + def root_set_weights( + self, + wallet: "bittensor.wallet", + netuids: Union[torch.LongTensor, list], + weights: Union[torch.FloatTensor, list], + version_key: int = 0, + wait_for_inclusion: bool = False, + wait_for_finalization: bool = False, + prompt: bool = False, + ) -> bool: + """Sets weights for the root network.""" + return set_root_weights_extrinsic( + subtensor=self, + wallet=wallet, + netuids=netuids, + weights=weights, + version_key=version_key, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + prompt=prompt, + ) + ######################## #### Standard Calls #### ######################## @@ -1031,6 +1432,84 @@ def make_substrate_call_with_retry(): return make_substrate_call_with_retry() + def state_call( + self, + method: str, + data: str, + block: Optional[int] = None, + ) -> Optional[object]: + @retry(delay=2, tries=3, backoff=2, max_delay=4) + def make_substrate_call_with_retry(): + with self.substrate as substrate: + block_hash = None if block == None else substrate.get_block_hash(block) + params = [method, data] + if block_hash: + params = params + [block_hash] + return substrate.rpc_request(method="state_call", params=params) + + return make_substrate_call_with_retry() + + def query_runtime_api( + self, + runtime_api: str, + method: str, + params: Optional[List[ParamWithTypes]], + block: Optional[int] = None, + ) -> Optional[bytes]: + """ + Returns a Scale Bytes type that should be decoded. + """ + call_definition = bittensor.__type_registry__["runtime_api"][runtime_api][ + "methods" + ][method] + + json_result = self.state_call( + method=f"{runtime_api}_{method}", + data="0x" + if params is None + else self._encode_params(call_definition=call_definition, params=params), + block=block, + ) + + if json_result is None: + return None + + return_type = call_definition["type"] + + as_scale_bytes = scalecodec.ScaleBytes(json_result["result"]) + + rpc_runtime_config = RuntimeConfiguration() + rpc_runtime_config.update_type_registry(load_type_registry_preset("legacy")) + rpc_runtime_config.update_type_registry(custom_rpc_type_registry) + + obj = rpc_runtime_config.create_scale_object(return_type, as_scale_bytes) + if obj.data.to_hex() == "0x0400": # RPC returned None result + return None + + return obj.decode() + + def _encode_params( + self, + call_definition: List[ParamWithTypes], + params: Union[List[Any], Dict[str, str]], + ) -> str: + """ + Returns a hex encoded string of the params using their types. + """ + param_data = scalecodec.ScaleBytes(b"") + + for i, param in enumerate(call_definition["params"]): + scale_obj = self.substrate.create_scale_object(param["type"]) + if type(params) is list: + param_data += scale_obj.encode(params[i]) + else: + if param["name"] not in params: + raise ValueError(f"Missing param {param['name']} in params dict.") + + param_data += scale_obj.encode(params[param["name"]]) + + return param_data.to_hex() + ##################################### #### Hyper parameter calls. #### ##################################### @@ -1060,14 +1539,10 @@ def difficulty(self, netuid: int, block: Optional[int] = None) -> Optional[int]: """ Returns network Burn hyper parameter """ - def burn( - self, netuid: int, block: Optional[int] = None - ) -> Optional[bittensor.Balance]: + def burn(self, netuid: int, block: Optional[int] = None) -> Optional[Balance]: if not self.subnet_exists(netuid, block): return None - return bittensor.Balance.from_rao( - self.query_subtensor("Burn", block, [netuid]).value - ) + return Balance.from_rao(self.query_subtensor("Burn", block, [netuid]).value) """ Returns network ImmunityPeriod hyper parameter """ @@ -1229,8 +1704,8 @@ def tempo(self, netuid: int, block: Optional[int] = None) -> int: def get_total_stake_for_hotkey( self, ss58_address: str, block: Optional[int] = None - ) -> Optional["bittensor.Balance"]: - return bittensor.Balance.from_rao( + ) -> Optional["Balance"]: + return Balance.from_rao( self.query_subtensor("TotalHotkeyStake", block, [ss58_address]).value ) @@ -1238,8 +1713,8 @@ def get_total_stake_for_hotkey( def get_total_stake_for_coldkey( self, ss58_address: str, block: Optional[int] = None - ) -> Optional["bittensor.Balance"]: - return bittensor.Balance.from_rao( + ) -> Optional["Balance"]: + return Balance.from_rao( self.query_subtensor("TotalColdkeyStake", block, [ss58_address]).value ) @@ -1247,8 +1722,8 @@ def get_total_stake_for_coldkey( def get_stake_for_coldkey_and_hotkey( self, hotkey_ss58: str, coldkey_ss58: str, block: Optional[int] = None - ) -> Optional["bittensor.Balance"]: - return bittensor.Balance.from_rao( + ) -> Optional["Balance"]: + return Balance.from_rao( self.query_subtensor("Stake", block, [hotkey_ss58, coldkey_ss58]).value ) @@ -1256,9 +1731,9 @@ def get_stake_for_coldkey_and_hotkey( def get_stake( self, hotkey_ss58: str, block: Optional[int] = None - ) -> List[Tuple[str, "bittensor.Balance"]]: + ) -> List[Tuple[str, "Balance"]]: return [ - (r[0].value, bittensor.Balance.from_rao(r[1].value)) + (r[0].value, Balance.from_rao(r[1].value)) for r in self.query_map_subtensor("Stake", block, [hotkey_ss58]) ] @@ -1283,18 +1758,18 @@ def get_hotkey_owner( """ Returns the axon information for this hotkey account """ def get_axon_info( - self, hotkey_ss58: str, block: Optional[int] = None - ) -> Optional[axon_info]: - result = self.query_subtensor("Axons", block, [hotkey_ss58]) + self, netuid: int, hotkey_ss58: str, block: Optional[int] = None + ) -> Optional[AxonInfo]: + result = self.query_subtensor("Axons", block, [netuid, hotkey_ss58]) if result != None: - return axon_info( - ip=bittensor.utils.networking.ip_from_int(result.value.ip), - ip_type=result.value.ip_type, - port=result.value.port, - protocol=result.value.protocol, - version=result.value.version, - placeholder1=result.value.placeholder1, - placeholder2=result.value.placeholder2, + return AxonInfo( + ip=bittensor.utils.networking.int_to_ip(result.value["ip"]), + ip_type=result.value["ip_type"], + port=result.value["port"], + protocol=result.value["protocol"], + version=result.value["version"], + placeholder1=result.value["placeholder1"], + placeholder2=result.value["placeholder2"], ) else: return None @@ -1302,16 +1777,16 @@ def get_axon_info( """ Returns the prometheus information for this hotkey account """ def get_prometheus_info( - self, hotkey_ss58: str, block: Optional[int] = None - ) -> Optional[axon_info]: - result = self.query_subtensor("Prometheus", block, [hotkey_ss58]) + self, netuid: int, hotkey_ss58: str, block: Optional[int] = None + ) -> Optional[AxonInfo]: + result = self.query_subtensor("Prometheus", block, [netuid, hotkey_ss58]) if result != None: return PrometheusInfo( - ip=bittensor.utils.networking.ip_from_int(result.value.ip), - ip_type=result.value.ip_type, - port=result.value.port, - version=result.value.version, - block=result.value.block, + ip=bittensor.utils.networking.int_to_ip(result.value["ip"]), + ip_type=result.value["ip_type"], + port=result.value["port"], + version=result.value["version"], + block=result.value["block"], ) else: return None @@ -1329,18 +1804,20 @@ def block(self) -> int: """ return self.get_current_block() - def total_issuance(self, block: Optional[int] = None) -> "bittensor.Balance": - return bittensor.Balance.from_rao( - self.query_subtensor("TotalIssuance", block).value - ) + def total_issuance(self, block: Optional[int] = None) -> "Balance": + return Balance.from_rao(self.query_subtensor("TotalIssuance", block).value) - def total_stake(self, block: Optional[int] = None) -> "bittensor.Balance": - return bittensor.Balance.from_rao( - self.query_subtensor("TotalStake", block).value - ) + def total_stake(self, block: Optional[int] = None) -> "Balance": + return Balance.from_rao(self.query_subtensor("TotalStake", block).value) - def serving_rate_limit(self, block: Optional[int] = None) -> Optional[int]: - return self.query_subtensor("ServingRateLimit", block).value + def serving_rate_limit( + self, netuid: int, block: Optional[int] = None + ) -> Optional[int]: + if not self.subnet_exists(netuid, block): + return None + return self.query_subtensor( + "ServingRateLimit", block=block, params=[netuid] + ).value def tx_rate_limit(self, block: Optional[int] = None) -> Optional[int]: return self.query_subtensor("TxRateLimit", block).value @@ -1378,7 +1855,7 @@ def get_subnet_connection_requirement( def get_emission_value_by_subnet( self, netuid: int, block: Optional[int] = None ) -> Optional[float]: - return bittensor.Balance.from_rao( + return Balance.from_rao( self.query_subtensor("EmissionValues", block, [netuid]).value ) @@ -1447,6 +1924,31 @@ def make_substrate_call_with_retry(): return SubnetInfo.from_vec_u8(result) + def get_subnet_hyperparameters( + self, netuid: int, block: Optional[int] = None + ) -> Optional[SubnetHyperparameters]: + hex_bytes_result = self.query_runtime_api( + runtime_api="SubnetInfoRuntimeApi", + method="get_subnet_hyperparams", + params=[netuid], + block=block, + ) + + if hex_bytes_result == None: + return [] + + if hex_bytes_result.startswith("0x"): + bytes_result = bytes.fromhex(hex_bytes_result[2:]) + else: + bytes_result = bytes.fromhex(hex_bytes_result) + + return SubnetHyperparameters.from_vec_u8(bytes_result) + + def get_subnet_owner( + self, netuid: int, block: Optional[int] = None + ) -> Optional[str]: + return self.query_subtensor("SubnetOwner", block, [netuid]).value + #################### #### Nomination #### #################### @@ -1486,8 +1988,7 @@ def make_substrate_call_with_retry(encoded_hotkey: List[int]): params=params, ) - hotkey_bytes: bytes = bittensor.utils.ss58_address_to_bytes(hotkey_ss58) - encoded_hotkey: List[int] = [int(byte) for byte in hotkey_bytes] + encoded_hotkey = ss58_to_vec_u8(hotkey_ss58) json_body = make_substrate_call_with_retry(encoded_hotkey) result = json_body["result"] @@ -1534,8 +2035,7 @@ def make_substrate_call_with_retry(encoded_coldkey: List[int]): params=params, ) - coldkey_bytes: bytes = bittensor.utils.ss58_address_to_bytes(coldkey_ss58) - encoded_coldkey: List[int] = [int(byte) for byte in coldkey_bytes] + encoded_coldkey = ss58_to_vec_u8(coldkey_ss58) json_body = make_substrate_call_with_retry(encoded_coldkey) result = json_body["result"] @@ -1544,6 +2044,59 @@ def make_substrate_call_with_retry(encoded_coldkey: List[int]): return DelegateInfo.delegated_list_from_vec_u8(result) + ########################### + #### Stake Information #### + ########################### + + def get_stake_info_for_coldkey( + self, coldkey_ss58: str, block: Optional[int] = None + ) -> List[StakeInfo]: + """Returns the list of StakeInfo objects for this coldkey""" + + encoded_coldkey = ss58_to_vec_u8(coldkey_ss58) + + hex_bytes_result = self.query_runtime_api( + runtime_api="StakeInfoRuntimeApi", + method="get_stake_info_for_coldkey", + params=[encoded_coldkey], + block=block, + ) + + if hex_bytes_result == None: + return None + + if hex_bytes_result.startswith("0x"): + bytes_result = bytes.fromhex(hex_bytes_result[2:]) + else: + bytes_result = bytes.fromhex(hex_bytes_result) + + return StakeInfo.list_from_vec_u8(bytes_result) + + def get_stake_info_for_coldkeys( + self, coldkey_ss58_list: List[str], block: Optional[int] = None + ) -> Dict[str, List[StakeInfo]]: + """Returns the list of StakeInfo objects for all coldkeys in the list.""" + encoded_coldkeys = [ + ss58_to_vec_u8(coldkey_ss58) for coldkey_ss58 in coldkey_ss58_list + ] + + hex_bytes_result = self.query_runtime_api( + runtime_api="StakeInfoRuntimeApi", + method="get_stake_info_for_coldkeys", + params=[encoded_coldkeys], + block=block, + ) + + if hex_bytes_result == None: + return None + + if hex_bytes_result.startswith("0x"): + bytes_result = bytes.fromhex(hex_bytes_result[2:]) + else: + bytes_result = bytes.fromhex(hex_bytes_result) + + return StakeInfo.list_of_tuple_from_vec_u8(bytes_result) + ######################################## #### Neuron information per subnet #### ######################################## @@ -1614,7 +2167,7 @@ def neuron_has_validator_permit( return self.query_subtensor("ValidatorPermit", block, [netuid, uid]).value def neuron_for_wallet( - self, wallet: "bittensor.Wallet", netuid=int, block: Optional[int] = None + self, wallet: "bittensor.wallet", netuid: int, block: Optional[int] = None ) -> Optional[NeuronInfo]: return self.get_neuron_for_pubkey_and_subnet( wallet.hotkey.ss58_address, netuid=netuid, block=block @@ -1702,25 +2255,25 @@ def neuron_for_uid_lite( if uid == None: return NeuronInfoLite._null_neuron() - @retry(delay=2, tries=3, backoff=2, max_delay=4) - def make_substrate_call_with_retry(): - with self.substrate as substrate: - block_hash = None if block == None else substrate.get_block_hash(block) - params = [netuid, uid] - if block_hash: - params = params + [block_hash] - return substrate.rpc_request( - method="neuronInfo_getNeuronLite", # custom rpc method - params=params, - ) - - json_body = make_substrate_call_with_retry() - result = json_body["result"] + hex_bytes_result = self.query_runtime_api( + runtime_api="NeuronInfoRuntimeApi", + method="get_neuron_lite", + params={ + "netuid": netuid, + "uid": uid, + }, + block=block, + ) - if result in (None, []): + if hex_bytes_result == None: return NeuronInfoLite._null_neuron() - return NeuronInfoLite.from_vec_u8(result) + if hex_bytes_result.startswith("0x"): + bytes_result = bytes.fromhex(hex_bytes_result[2:]) + else: + bytes_result = bytes.fromhex(hex_bytes_result) + + return NeuronInfoLite.from_vec_u8(bytes_result) def neurons_lite( self, netuid: int, block: Optional[int] = None @@ -1735,29 +2288,29 @@ def neurons_lite( neuron (List[NeuronInfoLite]): List of neuron lite metadata objects. """ + hex_bytes_result = self.query_runtime_api( + runtime_api="NeuronInfoRuntimeApi", + method="get_neurons_lite", + params=[netuid], + block=block, + ) - @retry(delay=2, tries=3, backoff=2, max_delay=4) - def make_substrate_call_with_retry(): - with self.substrate as substrate: - block_hash = None if block == None else substrate.get_block_hash(block) - params = [netuid] - if block_hash: - params = params + [block_hash] - return substrate.rpc_request( - method="neuronInfo_getNeuronsLite", # custom rpc method - params=params, - ) - - json_body = make_substrate_call_with_retry() - result = json_body["result"] - - if result in (None, []): + if hex_bytes_result == None: return [] - return NeuronInfoLite.list_from_vec_u8(result) + if hex_bytes_result.startswith("0x"): + bytes_result = bytes.fromhex(hex_bytes_result[2:]) + else: + bytes_result = bytes.fromhex(hex_bytes_result) + + return NeuronInfoLite.list_from_vec_u8(bytes_result) def metagraph( - self, netuid: int, lite: bool = True, block: Optional[int] = None + self, + netuid: int, + lite: bool = True, + block: Optional[int] = None, + root: bool = False, ) -> "bittensor.Metagraph": r"""Returns a synced metagraph for the subnet. Args: @@ -1774,10 +2327,32 @@ def metagraph( metagraph_ = bittensor.metagraph( network=self.network, netuid=netuid, lite=lite, sync=False ) - metagraph_.sync(block=block, lite=lite, subtensor=self) + metagraph_.sync(block=block, lite=lite, subtensor=self, root=root) return metagraph_ + def incentive(self, netuid: int, block: Optional[int] = None) -> List[int]: + """Returns a list of incentives for the subnet. + Args: + netuid ( int ): + The network uid of the subnet to query. + block ( Optional[int] ): + block to sync from, or None for latest block. + Returns: + i_map ( List[int] ): + The list of incentives for the subnet at the block, + indexed by UID. + """ + i_map = [] + i_map_encoded = self.query_map_subtensor(name="Incentive", block=block) + if i_map_encoded.records: + for netuid_, incentives_map in i_map_encoded: + if netuid_ == netuid: + i_map = incentives_map.serialize() + break + + return i_map + def weights( self, netuid: int, block: Optional[int] = None ) -> List[Tuple[int, List[Tuple[int, int]]]]: @@ -1804,6 +2379,53 @@ def bonds( return b_map + def associated_validator_ip_info( + self, netuid: int, block: Optional[int] = None + ) -> Optional[List[IPInfo]]: + """Returns the list of all validator IPs associated with this subnet. + + Args: + netuid (int): + The network uid of the subnet to query. + block ( Optional[int] ): + block to sync from, or None for latest block. + + Returns: + validator_ip_info (Optional[List[IPInfo]]): + List of validator IP info objects for subnet. + or None if no validator IPs are associated with this subnet, + e.g. if the subnet does not exist. + """ + hex_bytes_result = self.query_runtime_api( + runtime_api="ValidatorIPRuntimeApi", + method="get_associated_validator_ip_info_for_subnet", + params=[netuid], + block=block, + ) + + if hex_bytes_result == None: + return None + + if hex_bytes_result.startswith("0x"): + bytes_result = bytes.fromhex(hex_bytes_result[2:]) + else: + bytes_result = bytes.fromhex(hex_bytes_result) + + return IPInfo.list_from_vec_u8(bytes_result) + + def get_subnet_burn_cost(self, block: Optional[int] = None) -> int: + lock_cost = self.query_runtime_api( + runtime_api="SubnetRegistrationRuntimeApi", + method="get_network_registration_cost", + params=[], + block=block, + ) + + if lock_cost == None: + return None + + return lock_cost + ################ ## Extrinsics ## ################ @@ -1812,63 +2434,74 @@ def _do_delegation( self, wallet: "bittensor.wallet", delegate_ss58: str, - amount: "bittensor.Balance", + amount: "Balance", wait_for_inclusion: bool = True, wait_for_finalization: bool = False, ) -> bool: - with self.substrate as substrate: - call = substrate.compose_call( - call_module="SubtensorModule", - call_function="add_stake", - call_params={"hotkey": delegate_ss58, "amount_staked": amount.rao}, - ) - extrinsic = substrate.create_signed_extrinsic( - call=call, keypair=wallet.coldkey - ) - response = substrate.submit_extrinsic( - extrinsic, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) - # We only wait here if we expect finalization. - if not wait_for_finalization and not wait_for_inclusion: - return True - response.process_events() - if response.is_success: - return True - else: - raise StakeError(response.error_message) + @retry(delay=2, tries=3, backoff=2, max_delay=4) + def make_substrate_call_with_retry(): + with self.substrate as substrate: + call = substrate.compose_call( + call_module="SubtensorModule", + call_function="add_stake", + call_params={"hotkey": delegate_ss58, "amount_staked": amount.rao}, + ) + extrinsic = substrate.create_signed_extrinsic( + call=call, keypair=wallet.coldkey + ) + response = substrate.submit_extrinsic( + extrinsic, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + # We only wait here if we expect finalization. + if not wait_for_finalization and not wait_for_inclusion: + return True + response.process_events() + if response.is_success: + return True + else: + raise StakeError(response.error_message) + + return make_substrate_call_with_retry() def _do_undelegation( self, wallet: "bittensor.wallet", delegate_ss58: str, - amount: "bittensor.Balance", + amount: "Balance", wait_for_inclusion: bool = True, wait_for_finalization: bool = False, ) -> bool: - with self.substrate as substrate: - call = substrate.compose_call( - call_module="SubtensorModule", - call_function="remove_stake", - call_params={"hotkey": delegate_ss58, "amount_unstaked": amount.rao}, - ) - extrinsic = substrate.create_signed_extrinsic( - call=call, keypair=wallet.coldkey - ) - response = substrate.submit_extrinsic( - extrinsic, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) - # We only wait here if we expect finalization. - if not wait_for_finalization and not wait_for_inclusion: - return True - response.process_events() - if response.is_success: - return True - else: - raise StakeError(response.error_message) + @retry(delay=2, tries=3, backoff=2, max_delay=4) + def make_substrate_call_with_retry(): + with self.substrate as substrate: + call = substrate.compose_call( + call_module="SubtensorModule", + call_function="remove_stake", + call_params={ + "hotkey": delegate_ss58, + "amount_unstaked": amount.rao, + }, + ) + extrinsic = substrate.create_signed_extrinsic( + call=call, keypair=wallet.coldkey + ) + response = substrate.submit_extrinsic( + extrinsic, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + # We only wait here if we expect finalization. + if not wait_for_finalization and not wait_for_inclusion: + return True + response.process_events() + if response.is_success: + return True + else: + raise StakeError(response.error_message) + + return make_substrate_call_with_retry() def _do_nominate( self, @@ -1876,28 +2509,32 @@ def _do_nominate( wait_for_inclusion: bool = True, wait_for_finalization: bool = False, ) -> bool: - with self.substrate as substrate: - call = substrate.compose_call( - call_module="SubtensorModule", - call_function="become_delegate", - call_params={"hotkey": wallet.hotkey.ss58_address}, - ) - extrinsic = substrate.create_signed_extrinsic( - call=call, keypair=wallet.coldkey - ) # sign with coldkey - response = substrate.submit_extrinsic( - extrinsic, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) - # We only wait here if we expect finalization. - if not wait_for_finalization and not wait_for_inclusion: - return True - response.process_events() - if response.is_success: - return True - else: - raise NominationError(response.error_message) + @retry(delay=2, tries=3, backoff=2, max_delay=4) + def make_substrate_call_with_retry(): + with self.substrate as substrate: + call = substrate.compose_call( + call_module="SubtensorModule", + call_function="become_delegate", + call_params={"hotkey": wallet.hotkey.ss58_address}, + ) + extrinsic = substrate.create_signed_extrinsic( + call=call, keypair=wallet.coldkey + ) # sign with coldkey + response = substrate.submit_extrinsic( + extrinsic, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + # We only wait here if we expect finalization. + if not wait_for_finalization and not wait_for_inclusion: + return True + response.process_events() + if response.is_success: + return True + else: + raise NominationError(response.error_message) + + return make_substrate_call_with_retry() ################ #### Legacy #### @@ -1963,7 +2600,7 @@ def make_substrate_call_with_retry(): result = make_substrate_call_with_retry() return_dict = {} for r in result: - bal = bittensor.Balance(int(r[1]["data"]["free"].value)) + bal = Balance(int(r[1]["data"]["free"].value)) return_dict[r[0].value] = bal return return_dict diff --git a/bittensor/synapse.py b/bittensor/synapse.py new file mode 100644 index 0000000000..b9e3479e72 --- /dev/null +++ b/bittensor/synapse.py @@ -0,0 +1,661 @@ +# The MIT License (MIT) +# Copyright © 2021 Yuma Rao +# Copyright © 2022 Opentensor Foundation + +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the “Software”), to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of +# the Software. + +# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# 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 ast +import sys +import torch +import json +import base64 +import typing +import hashlib +import pydantic +from pydantic.schema import schema +import bittensor +from typing import Optional, List, Any + + +def get_size(obj, seen=None) -> int: + """ + Recursively finds size of objects. + + This function traverses every item of a given object and sums their sizes to compute the total size. + + Args: + obj (any type): The object to get the size of. + seen (set): Set of object ids that have been calculated. + + Returns: + int: The total size of the object. + + """ + size = sys.getsizeof(obj) + if seen is None: + seen = set() + obj_id = id(obj) + if obj_id in seen: + return 0 + # Important mark as seen *before* entering recursion to gracefully handle + # self-referential objects + seen.add(obj_id) + if isinstance(obj, dict): + size += sum([get_size(v, seen) for v in obj.values()]) + size += sum([get_size(k, seen) for k in obj.keys()]) + elif hasattr(obj, "__dict__"): + size += get_size(obj.__dict__, seen) + elif hasattr(obj, "__iter__") and not isinstance(obj, (str, bytes, bytearray)): + size += sum([get_size(i, seen) for i in obj]) + return size + + +def cast_int(raw: str) -> int: + """ + Converts a string to an integer, if the string is not None. + + This function attempts to convert a string to an integer. If the string is None, + it simply returns None. + + Args: + raw (str): The string to convert. + + Returns: + int or None: The converted integer, or None if the input was None. + + """ + return int(raw) if raw != None else raw + + +def cast_float(raw: str) -> float: + """ + Converts a string to a float, if the string is not None. + + This function attempts to convert a string to a float. If the string is None, + it simply returns None. + + Args: + raw (str): The string to convert. + + Returns: + float or None: The converted float, or None if the input was None. + + """ + return float(raw) if raw != None else raw + + +class TerminalInfo(pydantic.BaseModel): + class Config: + validate_assignment = True + + # The HTTP status code from: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status + status_code: Optional[int] = pydantic.Field( + title="status_code", + description="The HTTP status code from: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status", + examples=200, + default=None, + allow_mutation=True, + ) + _extract_status_code = pydantic.validator( + "status_code", pre=True, allow_reuse=True + )(cast_int) + + # The HTTP status code from: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status + status_message: Optional[str] = pydantic.Field( + title="status_message", + description="The status_message associated with the status_code", + examples="Success", + default=None, + allow_mutation=True, + ) + + # Process time on this terminal side of call + process_time: Optional[float] = pydantic.Field( + title="process_time", + description="Process time on this terminal side of call", + examples=0.1, + default=None, + allow_mutation=True, + ) + _extract_process_time = pydantic.validator( + "process_time", pre=True, allow_reuse=True + )(cast_float) + + # The terminal ip. + ip: Optional[str] = pydantic.Field( + title="ip", + description="The ip of the axon recieving the request.", + examples="198.123.23.1", + default=None, + allow_mutation=True, + ) + + # The host port of the terminal. + port: Optional[int] = pydantic.Field( + title="port", + description="The port of the terminal.", + examples="9282", + default=None, + allow_mutation=True, + ) + _extract_port = pydantic.validator("port", pre=True, allow_reuse=True)(cast_int) + + # The bittensor version on the terminal as an int. + version: Optional[int] = pydantic.Field( + title="version", + description="The bittensor version on the axon as str(int)", + examples=111, + default=None, + allow_mutation=True, + ) + _extract_version = pydantic.validator("version", pre=True, allow_reuse=True)( + cast_int + ) + + # A unique monotonically increasing integer nonce associate with the terminal + nonce: Optional[int] = pydantic.Field( + title="nonce", + description="A unique monotonically increasing integer nonce associate with the terminal generated from time.monotonic_ns()", + examples=111111, + default=None, + allow_mutation=True, + ) + _extract_nonce = pydantic.validator("nonce", pre=True, allow_reuse=True)(cast_int) + + # A unique identifier associated with the terminal, set on the axon side. + uuid: Optional[str] = pydantic.Field( + title="uuid", + description="A unique identifier associated with the terminal", + examples="5ecbd69c-1cec-11ee-b0dc-e29ce36fec1a", + default=None, + allow_mutation=True, + ) + + # The bittensor version on the terminal as an int. + hotkey: Optional[str] = pydantic.Field( + title="hotkey", + description="The ss58 encoded hotkey string of the terminal wallet.", + examples="5EnjDGNqqWnuL2HCAdxeEtN2oqtXZw6BMBe936Kfy2PFz1J1", + default=None, + allow_mutation=True, + ) + + # A signature verifying the tuple (axon_nonce, axon_hotkey, dendrite_hotkey, axon_uuid) + signature: Optional[str] = pydantic.Field( + title="signature", + description="A signature verifying the tuple (nonce, axon_hotkey, dendrite_hotkey, uuid)", + examples="0x0813029319030129u4120u10841824y0182u091u230912u", + default=None, + allow_mutation=True, + ) + + +class Synapse(pydantic.BaseModel): + class Config: + validate_assignment = True + + def deserialize(self) -> "Synapse": + """ + Deserializes the Synapse object. + + This method is intended to be overridden by subclasses for custom deserialization logic. + In the context of the Synapse superclass, this method simply returns the instance itself. + When inheriting from this class, subclasses should provide their own implementation for + deserialization if specific deserialization behavior is desired. + + By default, if a subclass does not provide its own implementation of this method, the + Synapse's deserialize method will be used, returning the object instance as-is. + + Returns: + Synapse: The deserialized Synapse object. In this default implementation, it returns the object itself. + """ + return self + + @pydantic.root_validator(pre=True) + def set_name_type(cls, values) -> dict: + values["name"] = cls.__name__ + return values + + # Defines the http route name which is set on axon.attach( callable( request: RequestName )) + name: Optional[str] = pydantic.Field( + title="name", + description="Defines the http route name which is set on axon.attach( callable( request: RequestName ))", + examples="Forward", + allow_mutation=True, + default=None, + repr=False, + ) + + # The call timeout, set by the dendrite terminal. + timeout: Optional[float] = pydantic.Field( + title="timeout", + description="Defines the total query length.", + examples=12.0, + default=12.0, + allow_mutation=True, + repr=False, + ) + _extract_timeout = pydantic.validator("timeout", pre=True, allow_reuse=True)( + cast_float + ) + + # The call timeout, set by the dendrite terminal. + total_size: Optional[int] = pydantic.Field( + title="total_size", + description="Total size of request body in bytes.", + examples=1000, + default=0, + allow_mutation=True, + repr=False, + ) + _extract_total_size = pydantic.validator("total_size", pre=True, allow_reuse=True)( + cast_int + ) + + # The call timeout, set by the dendrite terminal. + header_size: Optional[int] = pydantic.Field( + title="header_size", + description="Size of request header in bytes.", + examples=1000, + default=0, + allow_mutation=True, + repr=False, + ) + _extract_header_size = pydantic.validator( + "header_size", pre=True, allow_reuse=True + )(cast_int) + + # The dendrite Terminal Information. + dendrite: Optional[TerminalInfo] = pydantic.Field( + title="dendrite", + description="Dendrite Terminal Information", + examples="bittensor.TerminalInfo", + default=TerminalInfo(), + allow_mutation=True, + repr=False, + ) + + # A axon terminal information + axon: Optional[TerminalInfo] = pydantic.Field( + title="axon", + description="Axon Terminal Information", + examples="bittensor.TerminalInfo", + default=TerminalInfo(), + allow_mutation=True, + repr=False, + ) + + computed_body_hash: Optional[str] = pydantic.Field( + title="computed_body_hash", + description="The computed body hash of the request.", + examples="0x0813029319030129u4120u10841824y0182u091u230912u", + default="", + allow_mutation=False, + repr=False, + ) + + required_hash_fields: Optional[List[str]] = pydantic.Field( + title="required_hash_fields", + description="The list of required fields to compute the body hash.", + examples=["roles", "messages"], + default=[], + allow_mutation=False, + repr=False, + ) + + def __setattr__(self, name: str, value: Any): + """ + Override the __setattr__ method to make the `required_hash_fields` property read-only. + """ + if name == "body_hash": + raise AttributeError( + "body_hash property is read-only and cannot be overridden." + ) + super().__setattr__(name, value) + + def get_total_size(self) -> int: + """ + Get the total size of the current object. + + This method first calculates the size of the current object, then assigns it + to the instance variable self.total_size and finally returns this value. + + Returns: + int: The total size of the current object. + """ + self.total_size = get_size(self) + return self.total_size + + @property + def is_success(self) -> bool: + """ + Checks if the dendrite's status code indicates success. + + This method returns True if the status code of the dendrite is 200, + which typically represents a successful HTTP request. + + Returns: + bool: True if dendrite's status code is 200, False otherwise. + """ + return self.dendrite.status_code == 200 + + @property + def is_failure(self) -> bool: + """ + Checks if the dendrite's status code indicates failure. + + This method returns True if the status code of the dendrite is not 200, + which would mean the HTTP request was not successful. + + Returns: + bool: True if dendrite's status code is not 200, False otherwise. + """ + return self.dendrite.status_code != 200 + + @property + def is_timeout(self) -> bool: + """ + Checks if the dendrite's status code indicates a timeout. + + This method returns True if the status code of the dendrite is 408, + which is the HTTP status code for a request timeout. + + Returns: + bool: True if dendrite's status code is 408, False otherwise. + """ + return self.dendrite.status_code == 408 + + @property + def is_blacklist(self) -> bool: + """ + Checks if the dendrite's status code indicates a blacklisted request. + + This method returns True if the status code of the dendrite is 403, + which is the HTTP status code for a forbidden request. + + Returns: + bool: True if dendrite's status code is 403, False otherwise. + """ + return self.dendrite.status_code == 403 + + @property + def failed_verification(self) -> bool: + """ + Checks if the dendrite's status code indicates failed verification. + + This method returns True if the status code of the dendrite is 401, + which is the HTTP status code for unauthorized access. + + Returns: + bool: True if dendrite's status code is 401, False otherwise. + """ + return self.dendrite.status_code == 401 + + def to_headers(self) -> dict: + """ + This function constructs a dictionary of headers from the properties of the instance. + + Headers for 'name' and 'timeout' are directly taken from the instance. + Further headers are constructed from the properties 'axon' and 'dendrite'. + + If the object is a tensor, its shape and data type are added to the headers. + For non-optional objects, these are serialized and encoded before adding to the headers. + + Finally, the function adds the sizes of the headers and the total size to the headers. + + Returns: + dict: A dictionary of headers constructed from the properties of the instance. + """ + # Initializing headers with 'name' and 'timeout' + headers = {"name": self.name, "timeout": str(self.timeout)} + + # Adding headers for 'axon' and 'dendrite' if they are not None + headers.update( + { + f"bt_header_axon_{k}": str(v) + for k, v in self.axon.dict().items() + if v is not None + } + ) + headers.update( + { + f"bt_header_dendrite_{k}": str(v) + for k, v in self.dendrite.dict().items() + if v is not None + } + ) + + # Getting the type hints for the properties of the instance + property_type_hints = typing.get_type_hints(self) + + # Getting the fields of the instance + instance_fields = self.__dict__ + + # Iterating over the fields of the instance + for field, value in instance_fields.items(): + # If the object is not optional, serializing it, encoding it, and adding it to the headers + required = schema([self.__class__])["definitions"][self.name].get( + "required" + ) + + # Skipping the field if it's already in the headers or its value is None + if field in headers or value is None: + continue + + # Adding the tensor shape and data type to the headers if the object is a tensor + if isinstance(value, bittensor.Tensor): + headers[f"bt_header_tensor_{field}"] = f"{value.shape}-{value.dtype}" + + elif isinstance(value, list) and all( + isinstance(elem, bittensor.Tensor) for elem in value + ): + serialized_list_tensor = [] + for i, tensor in enumerate(value): + serialized_list_tensor.append(f"{tensor.shape}-{tensor.dtype}") + headers[f"bt_header_list_tensor_{field}"] = str(serialized_list_tensor) + + elif isinstance(value, dict) and all( + isinstance(elem, bittensor.Tensor) for elem in value.values() + ): + serialized_dict_tensor = [] + for key, tensor in value.items(): + serialized_dict_tensor.append( + f"{key}-{tensor.shape}-{tensor.dtype}" + ) + headers[f"bt_header_dict_tensor_{field}"] = str(serialized_dict_tensor) + + elif required and field in required: + try: + # create an empty (dummy) instance of type(value) to pass pydantic validation on the axon side + serialized_value = json.dumps(value.__class__.__call__()) + encoded_value = base64.b64encode(serialized_value.encode()).decode( + "utf-8" + ) + headers[f"bt_header_input_obj_{field}"] = encoded_value + except TypeError as e: + raise ValueError( + f"Error serializing {field} with value {value}. Objects must be json serializable." + ) from e + + # Adding the size of the headers and the total size to the headers + headers["header_size"] = str(sys.getsizeof(headers)) + headers["total_size"] = str(self.get_total_size()) + headers["computed_body_hash"] = self.body_hash + + return headers + + @property + def body_hash(self) -> str: + """ + Compute a SHA3-256 hash of the serialized body of the Synapse instance. + + The body of the Synapse instance comprises its serialized and encoded + non-optional fields. This property retrieves these fields using the + `required_fields_hash` field, then concatenates their string representations, + and finally computes a SHA3-256 hash of the resulting string. + + Returns: + str: The hexadecimal representation of the SHA3-256 hash of the instance's body. + """ + # Hash the body for verification + hashes = [] + + # Getting the fields of the instance + instance_fields = self.__dict__ + + for field, value in instance_fields.items(): + # If the field is required in the subclass schema, hash and add it. + if field in self.required_hash_fields: + hashes.append(bittensor.utils.hash(str(value))) + + # Hash and return the hashes that have been concatenated + return bittensor.utils.hash("".join(hashes)) + + @classmethod + def parse_headers_to_inputs(cls, headers: dict) -> dict: + """ + This class method parses a given headers dictionary to construct an inputs dictionary. + Different types of fields ('axon', 'dendrite', 'tensor', and 'input_obj') are identified + by their prefixes, extracted, and transformed appropriately. + Remaining fields are directly assigned. + + Args: + headers (dict): The dictionary of headers to parse + + Returns: + dict: The parsed inputs dictionary constructed from the headers + """ + + # Initialize the input dictionary with empty sub-dictionaries for 'axon' and 'dendrite' + inputs_dict = {"axon": {}, "dendrite": {}} + + # Iterate over each item in the headers + for key, value in headers.items(): + # Handle 'axon' headers + if "bt_header_axon_" in key: + try: + new_key = key.split("bt_header_axon_")[1] + inputs_dict["axon"][new_key] = value + except Exception as e: + bittensor.logging.error( + f"Error while parsing 'axon' header {key}: {e}" + ) + continue + # Handle 'dendrite' headers + elif "bt_header_dendrite_" in key: + try: + new_key = key.split("bt_header_dendrite_")[1] + inputs_dict["dendrite"][new_key] = value + except Exception as e: + bittensor.logging.error( + f"Error while parsing 'dendrite' header {key}: {e}" + ) + continue + # Handle 'tensor' headers + elif "bt_header_tensor_" in key: + try: + new_key = key.split("bt_header_tensor_")[1] + shape, dtype = value.split("-") + # TODO: Verify if the shape and dtype values need to be converted before being used + inputs_dict[new_key] = bittensor.Tensor(shape=shape, dtype=dtype) + except Exception as e: + bittensor.logging.error( + f"Error while parsing 'tensor' header {key}: {e}" + ) + continue + elif "bt_header_list_tensor_" in key: + try: + new_key = key.split("bt_header_list_tensor_")[1] + deserialized_tensors = [] + stensors = ast.literal_eval(value) + for value in stensors: + shape, dtype = value.split("-") + deserialized_tensors.append( + bittensor.Tensor(shape=shape, dtype=dtype) + ) + inputs_dict[new_key] = deserialized_tensors + except Exception as e: + bittensor.logging.error( + f"Error while parsing 'tensor' header {key}: {e}" + ) + continue + elif "bt_header_dict_tensor_" in key: + try: + new_key = key.split("bt_header_dict_tensor_")[1] + deserialized_dict_tensors = {} + stensors = ast.literal_eval(value) + for value in stensors: + key, shape, dtype = value.split("-") + deserialized_dict_tensors[key] = bittensor.Tensor( + shape=shape, dtype=dtype + ) + inputs_dict[new_key] = deserialized_dict_tensors + except Exception as e: + bittensor.logging.error( + f"Error while parsing 'tensor' header {key}: {e}" + ) + continue + # Handle 'input_obj' headers + elif "bt_header_input_obj" in key: + try: + new_key = key.split("bt_header_input_obj_")[1] + # Skip if the key already exists in the dictionary + if new_key in inputs_dict: + continue + # Decode and load the serialized object + inputs_dict[new_key] = json.loads( + base64.b64decode(value.encode()).decode("utf-8") + ) + except json.JSONDecodeError as e: + bittensor.logging.error( + f"Error while json decoding 'input_obj' header {key}: {e}" + ) + continue + except Exception as e: + bittensor.logging.error( + f"Error while parsing 'input_obj' header {key}: {e}" + ) + continue + else: + pass # TODO: log unexpected keys + + # Assign the remaining known headers directly + inputs_dict["timeout"] = headers.get("timeout", None) + inputs_dict["name"] = headers.get("name", None) + inputs_dict["header_size"] = headers.get("header_size", None) + inputs_dict["total_size"] = headers.get("total_size", None) + inputs_dict["computed_body_hash"] = headers.get("computed_body_hash", None) + + return inputs_dict + + @classmethod + def from_headers(cls, headers: dict) -> "Synapse": + """ + This class method creates an instance of the class from a given headers dictionary. + + Args: + headers (dict): The dictionary of headers to parse + + Returns: + Synapse: A new Synapse instance created from the parsed inputs + """ + + # Get the inputs dictionary from the headers + input_dict = cls.parse_headers_to_inputs(headers) + + # Use the dictionary unpacking operator to pass the inputs to the class constructor + synapse = cls(**input_dict) + + return synapse diff --git a/bittensor/tensor.py b/bittensor/tensor.py new file mode 100644 index 0000000000..50b39df4b2 --- /dev/null +++ b/bittensor/tensor.py @@ -0,0 +1,200 @@ +# The MIT License (MIT) +# Copyright © 2021 Yuma Rao +# Copyright © 2022 Opentensor Foundation + +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the “Software”), to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of +# the Software. + +# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# 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 numpy +import torch +import base64 +import pytest +import msgpack +import pydantic +import msgpack_numpy +from typing import Dict, Optional, Tuple, Union, List, Callable + +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, +} + + +def cast_dtype(raw: Union[None, torch.dtype, str]) -> str: + """ + Casts the raw value to a string representing the torch data type. + + Args: + raw (Union[None, torch.dtype, str]): The raw value to cast. + + Returns: + str: The string representing the torch 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] + elif isinstance(raw, str): + assert ( + raw in TORCH_DTYPES + ), f"{str} not a valid torch type in dict {TORCH_DTYPES}" + return raw + else: + raise Exception( + f"{raw} of type {type(raw)} does not have a valid type in Union[None, torch.dtype, str]" + ) + + +def cast_shape(raw: Union[None, List[int], str]) -> str: + """ + Casts the raw value to a string representing the tensor shape. + + Args: + raw (Union[None, List[int], str]): The raw value to cast. + + Returns: + str: The string representing the tensor shape. + + Raises: + Exception: If the raw value is of an invalid type or if the list elements are not of type int. + """ + if not raw: + return None + elif isinstance(raw, list): + if len(raw) == 0: + return raw + elif isinstance(raw[0], int): + return raw + else: + raise Exception(f"{raw} list elements are not of type int") + elif isinstance(raw, str): + shape = list(map(int, raw.split("[")[1].split("]")[0].split(","))) + return shape + else: + raise Exception( + f"{raw} of type {type(raw)} does not have a valid type in Union[None, List[int], str]" + ) + + +class tensor: + def __new__(cls, tensor: Union[list, numpy.ndarray, torch.Tensor]): + if isinstance(tensor, list): + tensor = torch.tensor(tensor) + elif isinstance(tensor, numpy.ndarray): + tensor = torch.tensor(tensor) + return Tensor.serialize(tensor=tensor) + + +class Tensor(pydantic.BaseModel): + """ + Represents a Tensor object. + + Attributes: + buffer (Optional[str]): Tensor buffer data. + dtype (str): Tensor data type. + shape (List[int]): Tensor shape. + """ + + class Config: + validate_assignment = True + + def tensor(self) -> torch.Tensor: + return self.deserialize() + + def tolist(self) -> List[object]: + return self.deserialize().tolist() + + def numpy(self) -> "numpy.ndarray": + return self.deserialize().detach().numpy() + + def deserialize(self) -> "torch.Tensor": + """ + Deserializes the Tensor object. + + Returns: + torch.Tensor: The deserialized tensor object. + + Raises: + Exception: If the deserialization process encounters an error. + """ + shape = tuple(self.shape) + buffer_bytes = base64.b64decode(self.buffer.encode("utf-8")) + numpy_object = msgpack.unpackb( + buffer_bytes, object_hook=msgpack_numpy.decode + ).copy() + torch_object = torch.as_tensor(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]) + + @staticmethod + def serialize(tensor: "torch.Tensor") -> "Tensor": + """ + Serializes the given tensor. + + Args: + tensor (torch.Tensor): The tensor to serialize. + + Returns: + Tensor: The serialized tensor. + + Raises: + Exception: If the serialization process encounters an error. + """ + dtype = str(tensor.dtype) + 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) + ).decode("utf-8") + return Tensor(buffer=data_buffer, shape=shape, dtype=dtype) + + buffer: Optional[str] = pydantic.Field( + title="buffer", + description="Tensor buffer data. This field stores the serialized representation of the tensor data.", + examples="0x321e13edqwds231231231232131", + allow_mutation=False, + repr=False, + ) # Represents the tensor buffer data. + + 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", + allow_mutation=False, + repr=True, + ) # Represents the data type of the tensor. + _extract_dtype = pydantic.validator("dtype", pre=True, allow_reuse=True)(cast_dtype) + + shape: List[int] = pydantic.Field( + title="shape", + description="Tensor shape. This field defines the dimensions of the tensor as a list of integers, such as [10, 10] for a 2D tensor with shape (10, 10).", + examples="[10,10]", + allow_mutation=False, + repr=True, + ) # Represents the shape of the tensor. + _extract_shape = pydantic.validator("shape", pre=True, allow_reuse=True)(cast_shape) diff --git a/bittensor/_threadpool/priority_thread_pool_impl.py b/bittensor/threadpool.py similarity index 84% rename from bittensor/_threadpool/priority_thread_pool_impl.py rename to bittensor/threadpool.py index cb5bea70d6..7cc1b6a9a3 100644 --- a/bittensor/_threadpool/priority_thread_pool_impl.py +++ b/bittensor/threadpool.py @@ -7,15 +7,18 @@ import os import sys -import bittensor -from concurrent.futures import _base -import itertools +import time import queue import random -import threading import weakref -import time +import argparse +import bittensor +import itertools +import threading + from loguru import logger +from typing import Callable +from concurrent.futures import _base # Workers are created as daemon threads. This is done to allow the interpreter # to exit when there are still idle threads in a ThreadPoolExecutor's thread @@ -157,11 +160,51 @@ def __init__( self._initializer = initializer self._initargs = initargs + @classmethod + def add_args(cls, parser: argparse.ArgumentParser, prefix: str = None): + """Accept specific arguments from parser""" + prefix_str = "" if prefix == None else prefix + "." + try: + default_max_workers = ( + os.getenv("BT_PRIORITY_MAX_WORKERS") + if os.getenv("BT_PRIORITY_MAX_WORKERS") != None + else 5 + ) + default_maxsize = ( + os.getenv("BT_PRIORITY_MAXSIZE") + if os.getenv("BT_PRIORITY_MAXSIZE") != None + else 10 + ) + parser.add_argument( + "--" + prefix_str + "priority.max_workers", + type=int, + help="""maximum number of threads in thread pool""", + default=default_max_workers, + ) + parser.add_argument( + "--" + prefix_str + "priority.maxsize", + type=int, + help="""maximum size of tasks in priority queue""", + default=default_maxsize, + ) + except argparse.ArgumentError: + # re-parsing arguments. + pass + + @classmethod + def config(cls) -> "bittensor.config": + """Get config from the argument parser + Return: bittensor.config object + """ + parser = argparse.ArgumentParser() + PriorityThreadPoolExecutor.add_args(parser) + return bittensor.config(parser, args=[]) + @property def is_empty(self): return self._work_queue.empty() - def submit(self, fn, *args, **kwargs): + def submit(self, fn: Callable, *args, **kwargs) -> _base.Future: with self._shutdown_lock: if self._broken: raise BrokenThreadPool(self._broken) diff --git a/bittensor/_subtensor/types.py b/bittensor/types.py similarity index 100% rename from bittensor/_subtensor/types.py rename to bittensor/types.py diff --git a/bittensor/utils/__init__.py b/bittensor/utils/__init__.py index e52b06b07a..6324461965 100644 --- a/bittensor/utils/__init__.py +++ b/bittensor/utils/__init__.py @@ -1,49 +1,42 @@ -import numbers +# The MIT License (MIT) +# Copyright © 2022 Opentensor Foundation +# Copyright © 2023 Opentensor Technologies Inc + +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the “Software”), to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of +# the Software. + +# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# 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. + from typing import Callable, Union, List, Optional, Dict, Literal, Type, Any import bittensor -import pandas +import hashlib import requests import torch import scalecodec -import argparse -from substrateinterface.utils import ss58 -from bittensor_wallet.utils import * +from substrateinterface.utils import ss58 as ss58 -from .registration import create_pow, __reregister_wallet as reregister +from .wallet_utils import * +from .registration import create_pow as create_pow, __reregister_wallet as reregister RAOPERTAO = 1e9 U16_MAX = 65535 U64_MAX = 18446744073709551615 -def indexed_values_to_dataframe( - prefix: Union[str, int], - index: Union[list, torch.LongTensor], - values: Union[list, torch.Tensor], - filter_zeros: bool = False, -) -> "pandas.DataFrame": - # Type checking. - if not isinstance(prefix, str) and not isinstance(prefix, numbers.Number): - raise ValueError("Passed prefix must have type str or Number") - if isinstance(prefix, numbers.Number): - prefix = str(prefix) - if not isinstance(index, list) and not isinstance(index, torch.Tensor): - raise ValueError("Passed uids must have type list or torch.Tensor") - if not isinstance(values, list) and not isinstance(values, torch.Tensor): - raise ValueError("Passed values must have type list or torch.Tensor") - if not isinstance(index, list): - index = index.tolist() - if not isinstance(values, list): - values = values.tolist() - - index = [idx_i for idx_i in index if idx_i < len(values) and idx_i >= 0] - dataframe = pandas.DataFrame(columns=[prefix], index=index) - for idx_i in index: - value_i = values[idx_i] - if value_i > 0 or not filter_zeros: - dataframe.loc[idx_i] = pandas.Series({str(prefix): value_i}) - return dataframe +def ss58_to_vec_u8(ss58_address: str) -> List[int]: + ss58_bytes: bytes = bittensor.utils.ss58_address_to_bytes(ss58_address) + encoded_address: List[int] = [int(byte) for byte in ss58_bytes] + return encoded_address def unbiased_topk(values, k, dim=0, sorted=True, largest=True): @@ -70,7 +63,7 @@ def unbiased_topk(values, k, dim=0, sorted=True, largest=True): def version_checking(timeout: int = 15): try: - bittensor.logging.info( + bittensor.logging.debug( f"Checking latest Bittensor version at: {bittensor.__pipaddress__}" ) response = requests.get(bittensor.__pipaddress__, timeout=timeout) @@ -207,10 +200,11 @@ def u8_key_to_ss58(u8_key: List[int]) -> str: return scalecodec.ss58_encode(bytes(u8_key).hex(), bittensor.__ss58_format__) -def type_or_suppress( - type_func: Callable[[str], Any] -) -> Callable[[str], Union[Any, Literal["==SUPRESS=="]]]: - def _type_or_suppress(value: str) -> Union[Any, Literal["==SUPRESS=="]]: - return value if value == argparse.SUPPRESS else type_func(value) +def hash(content, encoding="utf-8"): + sha3 = hashlib.sha3_256() + + # Update the hash object with the concatenated string + sha3.update(content.encode(encoding)) - return _type_or_suppress + # Produce the hash + return sha3.hexdigest() diff --git a/bittensor/utils/balance.py b/bittensor/utils/balance.py index ccf4262660..63ca6cd5ba 100644 --- a/bittensor/utils/balance.py +++ b/bittensor/utils/balance.py @@ -1,5 +1,7 @@ # The MIT License (MIT) -# Copyright © 2021 Yuma Rao +# Copyright © 2021-2022 Yuma Rao +# Copyright © 2022 Opentensor Foundation +# Copyright © 2023 Opentensor Technologies Inc # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated # documentation files (the “Software”), to deal in the Software without restriction, including without limitation @@ -14,6 +16,7 @@ # 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. + from typing import Union import bittensor @@ -21,11 +24,15 @@ class Balance: """ - Represents the bittensor balance of the wallet, stored as rao (int) - The Balance object is immutable, and can be used as a number or as a string - Can only guarantee that the balance is accurate to 9 decimal places (tao) - - Note: In operations between Balance and int/float, the other value is assumed to be in rao + Represents the bittensor balance of the wallet, stored as rao (int). + This class provides a way to interact with balances in two different units: rao and tao. + It provides methods to convert between these units, as well as to perform arithmetic and comparison operations. + + Attributes: + unit: A string representing the symbol for the tao unit. + rao_unit: A string representing the symbol for the rao unit. + rao: An integer that stores the balance in rao units. + tao: A float property that gives the balance in tao units. """ unit: str = bittensor.__tao_symbol__ # This is the tao unit @@ -34,6 +41,13 @@ class Balance: tao: float def __init__(self, balance: Union[int, float]): + """ + Initialize a Balance object. If balance is an int, it's assumed to be in rao. + If balance is a float, it's assumed to be in tao. + + Args: + balance: The initial balance, in either rao (if an int) or tao (if a float). + """ if isinstance(balance, int): self.rao = balance elif isinstance(balance, float): @@ -47,12 +61,21 @@ def tao(self): return self.rao / pow(10, 9) def __int__(self): + """ + Convert the Balance object to an int. The resulting value is in rao. + """ return self.rao def __float__(self): + """ + Convert the Balance object to a float. The resulting value is in tao. + """ return self.tao def __str__(self): + """ + Returns the Balance object as a string in the format "symbolvalue", where the value is in tao. + """ return f"{self.unit}{float(self.tao):,.9f}" def __rich__(self): @@ -223,17 +246,40 @@ def __abs__(self): @staticmethod def from_float(amount: float): - """Given tao (float), return Balance object with rao(int) and tao(float), where rao = int(tao*pow(10,9))""" + """ + Given tao (float), return Balance object with rao(int) and tao(float), where rao = int(tao*pow(10,9)) + Args: + amount: The amount in tao. + + Returns: + A Balance object representing the given amount. + """ rao = int(amount * pow(10, 9)) return Balance(rao) @staticmethod def from_tao(amount: float): - """Given tao (float), return Balance object with rao(int) and tao(float), where rao = int(tao*pow(10,9))""" + """ + Given tao (float), return Balance object with rao(int) and tao(float), where rao = int(tao*pow(10,9)) + + Args: + amount: The amount in tao. + + Returns: + A Balance object representing the given amount. + """ rao = int(amount * pow(10, 9)) return Balance(rao) @staticmethod def from_rao(amount: int): - """Given rao (int), return Balance object with rao(int) and tao(float), where rao = int(tao*pow(10,9))""" + """ + Given rao (int), return Balance object with rao(int) and tao(float), where rao = int(tao*pow(10,9)) + + Args: + amount: The amount in rao. + + Returns: + A Balance object representing the given amount. + """ return Balance(amount) diff --git a/bittensor/utils/codes.py b/bittensor/utils/codes.py deleted file mode 100644 index 7b9a1a3b5f..0000000000 --- a/bittensor/utils/codes.py +++ /dev/null @@ -1,153 +0,0 @@ -""" utils for rpc log, convert return code to string with color for the log -""" -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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. - -from loguru import logger -import bittensor - -logger = logger.opt(colors=True) - - -def code_to_string(code: "bittensor.proto.ReturnCode") -> str: - """Return code -> string""" - if code == 0: - return "NoReturn" - elif code == 1: - return "Success" - elif code == 2: - return "Timeout" - elif code == 3: - return "Backoff" - elif code == 4: - return "Unavailable" - elif code == 5: - return "NotImplemented" - elif code == 6: - return "EmptyRequest" - elif code == 7: - return "EmptyResponse" - elif code == 8: - return "InvalidResponse" - elif code == 9: - return "InvalidRequest" - elif code == 10: - return "RequestShapeException" - elif code == 11: - return "ResponseShapeException" - elif code == 12: - return "RequestSerializationException" - elif code == 13: - return "ResponseSerializationException" - elif code == 14: - return "RequestDeserializationException" - elif code == 15: - return "ResponseDeserializationException" - elif code == 16: - return "NotServingNucleus" - elif code == 17: - return "NucleusTimeout" - elif code == 18: - return "NucleusFull" - elif code == 19: - return "RequestIncompatibleVersion" - elif code == 20: - return "ResponseIncompatibleVersion" - elif code == 21: - return "SenderUnknown" - elif code == 22: - return "UnknownException" - elif code == 23: - return "Unauthenticated" - elif code == 24: - return "BadEndpoint" - elif code == 25: - return "Blacklisted" - else: - return "UnknownCode" - - -def code_to_loguru_color(code: "bittensor.proto.ReturnCode") -> str: - """Return code -> loguru color""" - if code == 0: - return "red" - elif code == 1: - return "green" - elif code == 2: - return "yellow" - elif code == 3: - return "yellow" - elif code == 4: - return "red" - elif code == 5: - return "red" - elif code == 6: - return "red" - elif code == 7: - return "red" - elif code == 8: - return "red" - elif code == 9: - return "red" - elif code == 10: - return "red" - elif code == 11: - return "red" - elif code == 12: - return "red" - elif code == 13: - return "red" - elif code == 14: - return "red" - elif code == 15: - return "red" - elif code == 16: - return "red" - elif code == 17: - return "yellow" - elif code == 18: - return "yellow" - elif code == 19: - return "red" - elif code == 20: - return "red" - elif code == 21: - return "red" - elif code == 22: - return "red" - elif code == 23: - return "red" - elif code == 24: - return "red" - elif code == 25: - return "magenta" - else: - return "red" - - -def code_to_synapse(code: "bittensor.proto.Synapse.SynapseType"): - """Return Code -> Synapse Type""" - if code == 1: - return "text_last_hidden_state" - elif code == 2: - return "text_causal_lm" - elif code == 3: - return "text_seq_2_seq" - elif code == 4: - return "text_causal_lm_next" - else: - return "Null" diff --git a/bittensor/utils/networking.py b/bittensor/utils/networking.py index 07cef927e2..d8e1b79e9f 100644 --- a/bittensor/utils/networking.py +++ b/bittensor/utils/networking.py @@ -1,7 +1,9 @@ """ Utils for handling local network with ip and ports. """ # The MIT License (MIT) -# Copyright © 2021 Yuma Rao +# Copyright © 2021-2022 Yuma Rao +# Copyright © 2022-2023 Opentensor Foundation +# Copyright © 2023 Opentensor Technologies # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated # documentation files (the “Software”), to deal in the Software without restriction, including without limitation diff --git a/bittensor/utils/registration.py b/bittensor/utils/registration.py index c5cb91b587..d86f7d85fc 100644 --- a/bittensor/utils/registration.py +++ b/bittensor/utils/registration.py @@ -57,7 +57,7 @@ class POWSolution: difficulty: int seal: bytes - def is_stale(self, subtensor: "bittensor.Subtensor") -> bool: + def is_stale(self, subtensor: "bittensor.subtensor") -> bool: """Returns True if the POW is stale. This means the block the POW is solved for is within 3 blocks of the current block. """ @@ -444,14 +444,12 @@ def update(self, stats: RegistrationStatistics, verbose: bool = False) -> None: if self.status is not None: self.status.update(self.get_status_message(stats, verbose=verbose)) else: - self.console.log( - self.get_status_message(stats, verbose=verbose), - ) + self.console.log(self.get_status_message(stats, verbose=verbose)) def _solve_for_difficulty_fast( subtensor, - wallet: "bittensor.Wallet", + wallet: "bittensor.wallet", netuid: int, output_in_place: bool = True, num_processes: Optional[int] = None, @@ -466,7 +464,7 @@ def _solve_for_difficulty_fast( subtensor Subtensor to connect to for block information and to submit. wallet: - Wallet to use for registration. + wallet to use for registration. netuid: int The netuid of the subnet to register to. output_in_place: bool @@ -507,7 +505,9 @@ def _solve_for_difficulty_fast( finished_queues = [multiprocessing.Queue() for _ in range(num_processes)] check_block = multiprocessing.Lock() - hotkey_bytes = wallet.hotkey.public_key + hotkey_bytes = ( + wallet.coldkeypub.public_key if netuid == -1 else wallet.hotkey.public_key + ) # Start consumers solvers = [ _Solver( @@ -578,9 +578,8 @@ def _solve_for_difficulty_fast( hash_rates = [0] * n_samples # The last n true hash_rates weights = [alpha_**i for i in range(n_samples)] # weights decay by alpha - while not subtensor.is_hotkey_registered( - netuid=netuid, - hotkey_ss58=wallet.hotkey.ss58_address, + while netuid == -1 or not subtensor.is_hotkey_registered( + netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address ): # Wait until a solver finds a solution try: @@ -659,13 +658,13 @@ def _solve_for_difficulty_fast( @backoff.on_exception(backoff.constant, Exception, interval=1, max_tries=3) def _get_block_with_retry( - subtensor: "bittensor.Subtensor", netuid: int + subtensor: "bittensor.subtensor", netuid: int ) -> Tuple[int, int, bytes]: """ Gets the current block number, difficulty, and block hash from the substrate node. Args: - subtensor (:obj:`bittensor.Subtensor`, `required`): + subtensor (:obj:`bittensor.subtensor`, `required`): The subtensor object to use to get the block number, difficulty, and block hash. netuid (:obj:`int`, `required`): @@ -686,7 +685,7 @@ def _get_block_with_retry( ValueError: If the difficulty is None. """ block_number = subtensor.get_current_block() - difficulty = subtensor.difficulty(netuid=netuid) + difficulty = 1_000_000 if netuid == -1 else subtensor.difficulty(netuid=netuid) block_hash = subtensor.get_block_hash(block_number) if block_hash is None: raise Exception( @@ -715,7 +714,7 @@ def __exit__(self, *args): def _check_for_newest_block_and_update( - subtensor: "bittensor.Subtensor", + subtensor: "bittensor.subtensor", netuid: int, old_block_number: int, hotkey_bytes: bytes, @@ -731,7 +730,7 @@ def _check_for_newest_block_and_update( Checks for a new block and updates the current block information if a new block is found. Args: - subtensor (:obj:`bittensor.Subtensor`, `required`): + subtensor (:obj:`bittensor.subtensor`, `required`): The subtensor object to use for getting the current block. netuid (:obj:`int`, `required`): The netuid to use for retrieving the difficulty. @@ -790,8 +789,8 @@ def _check_for_newest_block_and_update( def _solve_for_difficulty_fast_cuda( - subtensor: "bittensor.Subtensor", - wallet: "bittensor.Wallet", + subtensor: "bittensor.subtensor", + wallet: "bittensor.wallet", netuid: int, output_in_place: bool = True, update_interval: int = 50_000, @@ -804,9 +803,9 @@ def _solve_for_difficulty_fast_cuda( """ Solves the registration fast using CUDA Args: - subtensor: bittensor.Subtensor + subtensor: bittensor.subtensor The subtensor node to grab blocks - wallet: bittensor.Wallet + wallet: bittensor.wallet The wallet to register netuid: int The netuid of the subnet to register to. @@ -926,9 +925,8 @@ def _solve_for_difficulty_fast_cuda( weights = [alpha_**i for i in range(n_samples)] # weights decay by alpha solution = None - while not subtensor.is_hotkey_registered( - netuid=netuid, - hotkey_ss58=wallet.hotkey.ss58_address, + while netuid == -1 or not subtensor.is_hotkey_registered( + netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address ): # Wait until a solver finds a solution try: @@ -1030,9 +1028,9 @@ def create_pow( """ Creates a proof of work for the given subtensor and wallet. Args: - subtensor (:obj:`bittensor.subtensor.Subtensor`, `required`): + subtensor (:obj:`bittensor.subtensor.subtensor`, `required`): The subtensor to create a proof of work for. - wallet (:obj:`bittensor.wallet.Wallet`, `required`): + wallet (:obj:`bittensor.wallet.wallet`, `required`): The wallet to create a proof of work for. netuid (:obj:`int`, `required`): The netuid for the subnet to create a proof of work for. @@ -1062,8 +1060,9 @@ def create_pow( Raises: :obj:`ValueError`: If the subnet does not exist. """ - if not subtensor.subnet_exists(netuid=netuid): - raise ValueError(f"Subnet {netuid} does not exist") + if netuid != -1: + if not subtensor.subnet_exists(netuid=netuid): + raise ValueError(f"Subnet {netuid} does not exist") if cuda: solution: Optional[POWSolution] = _solve_for_difficulty_fast_cuda( @@ -1092,20 +1091,20 @@ def create_pow( def __reregister_wallet( netuid: int, - wallet: "bittensor.Wallet", - subtensor: "bittensor.Subtensor", + wallet: "bittensor.wallet", + subtensor: "bittensor.subtensor", reregister: bool = False, prompt: bool = False, **registration_args: Any, -) -> Optional["bittensor.Wallet"]: - """Re-register this a Wallet on the chain, or exits. +) -> Optional["bittensor.wallet"]: + """Re-register this a wallet on the chain, or exits. Exits if the wallet is not registered on the chain AND reregister is set to False. Args: netuid (int): The network uid of the subnet to register on. - wallet( 'bittensor.Wallet' ): - Bittensor Wallet to re-register + wallet( 'bittensor.wallet' ): + Bittensor wallet to re-register reregister (bool, default=False): If true, re-registers the wallet on the chain. Exits if False and the wallet is not registered on the chain. @@ -1114,7 +1113,7 @@ def __reregister_wallet( **registration_args (Any): The registration arguments to pass to the subtensor register function. Return: - wallet (bittensor.Wallet): + wallet (bittensor.wallet): The wallet Raises: @@ -1132,10 +1131,7 @@ def __reregister_wallet( sys.exit(0) subtensor.register( - wallet=wallet, - netuid=netuid, - prompt=prompt, - **registration_args, + wallet=wallet, netuid=netuid, prompt=prompt, **registration_args ) return wallet diff --git a/bittensor/utils/registratrion_old.py b/bittensor/utils/registratrion_old.py index 1fec95255b..453772d649 100644 --- a/bittensor/utils/registratrion_old.py +++ b/bittensor/utils/registratrion_old.py @@ -91,7 +91,7 @@ def millify(n: int): return "{:.2f}{}".format(n / 10 ** (3 * millidx), millnames[millidx]) -def POWNotStale(subtensor: "bittensor.Subtensor", pow_result: Dict) -> bool: +def POWNotStale(subtensor: "bittensor.subtensor", pow_result: Dict) -> bool: """Returns True if the POW is not stale. This means the block the POW is solved for is within 3 blocks of the current block. """ @@ -468,13 +468,11 @@ def update(self, stats: RegistrationStatistics, verbose: bool = False) -> None: if self.status is not None: self.status.update(self.get_status_message(stats, verbose=verbose)) else: - self.console.log( - self.get_status_message(stats, verbose=verbose), - ) + self.console.log(self.get_status_message(stats, verbose=verbose)) def solve_for_difficulty_fast( - subtensor: "bittensor.Subtensor", + subtensor: "bittensor.subtensor", wallet, output_in_place: bool = True, num_processes: Optional[int] = None, @@ -600,9 +598,7 @@ def solve_for_difficulty_fast( hash_rates = [0] * n_samples # The last n true hash_rates weights = [alpha_**i for i in range(n_samples)] # weights decay by alpha - while not subtensor.is_hotkey_registered( - hotkey_ss58=wallet.hotkey.ss58_address, - ): + while not subtensor.is_hotkey_registered(hotkey_ss58=wallet.hotkey.ss58_address): # Wait until a solver finds a solution try: solution = solution_queue.get(block=True, timeout=0.25) @@ -677,7 +673,7 @@ def solve_for_difficulty_fast( @backoff.on_exception(backoff.constant, Exception, interval=1, max_tries=3) -def get_block_with_retry(subtensor: "bittensor.Subtensor") -> Tuple[int, int, bytes]: +def get_block_with_retry(subtensor: "bittensor.subtensor") -> Tuple[int, int, bytes]: block_number = subtensor.get_current_block() difficulty = subtensor.difficulty block_hash = subtensor.substrate.get_block_hash(block_number) @@ -706,7 +702,7 @@ def __exit__(self, *args): def check_for_newest_block_and_update( - subtensor: "bittensor.Subtensor", + subtensor: "bittensor.subtensor", old_block_number: int, curr_diff: multiprocessing.Array, curr_block: multiprocessing.Array, @@ -719,7 +715,7 @@ def check_for_newest_block_and_update( """ Checks for a new block and updates the current block information if a new block is found. Args: - subtensor (:obj:`bittensor.Subtensor`, `required`): + subtensor (:obj:`bittensor.subtensor`, `required`): The subtensor object to use for getting the current block. old_block_number (:obj:`int`, `required`): The old block number to check against. @@ -773,8 +769,8 @@ def check_for_newest_block_and_update( def solve_for_difficulty_fast_cuda( - subtensor: "bittensor.Subtensor", - wallet: "bittensor.Wallet", + subtensor: "bittensor.subtensor", + wallet: "bittensor.wallet", output_in_place: bool = True, update_interval: int = 50_000, TPB: int = 512, @@ -786,9 +782,9 @@ def solve_for_difficulty_fast_cuda( """ Solves the registration fast using CUDA Args: - subtensor: bittensor.Subtensor + subtensor: bittensor.subtensor The subtensor node to grab blocks - wallet: bittensor.Wallet + wallet: bittensor.wallet The wallet to register output_in_place: bool If true, prints the output in place, otherwise prints to new lines @@ -908,7 +904,7 @@ def solve_for_difficulty_fast_cuda( solution = None while not subtensor.is_hotkey_registered( - hotkey_ss58=wallet.hotkey.ss58_address, + hotkey_ss58=wallet.hotkey.ss58_address ): # Wait until a solver finds a solution try: diff --git a/bittensor/utils/tokenizer_utils.py b/bittensor/utils/tokenizer_utils.py deleted file mode 100644 index e39e021d5b..0000000000 --- a/bittensor/utils/tokenizer_utils.py +++ /dev/null @@ -1,1793 +0,0 @@ -""" Utils for tokenizer equivalence checking, logit translation, etc. -""" -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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 - -from typing import List, Dict, Tuple, Any, Union -from transformers import PreTrainedTokenizerBase - -EPSILON = 1e-40 - - -def get_tokenizer_alignment_splits( - offset_mapping: List[tuple], offset_mapping_std: List[tuple] -) -> Dict[int, tuple]: - r""" - Calculates split depths necessary for tokens to align input offsets to standard offsets. - Only input offsets may be split, not standard offsets, to create one-to-one, one-to-many, or many-to-one - token alignments between input-to-standard tokenization. - Allows for multiple depth splits on a token. - Args: - offset_mapping (:obj:`List[tuple]`, `required`): - Tokenizer offset mappings for a specific sequence [(left_0, right_0), (left_1, right_1), ...]. - offset_mapping_std (:obj:`List[tuple]`, `required`): - Standard tokenizer offset mappings for a specific sequence [(left_0, right_0), (left_1, right_1), ...] - - Returns: - splits (:obj:`Dict[int, tuple]`, `required`): - For tokens that have to be split, {Token index: (split depth 1, split depth 2, ...), ...}. - """ - - splits = {} - idx = 0 # index of token segment (server tokenization) - idx_std = 0 # index of token segment (standard tokenization) - - right = offset_mapping[idx][1] # first right edge - right_std = offset_mapping_std[idx_std][1] # first std right edge - - while ( - idx + 1 < len(offset_mapping) and offset_mapping[idx + 1][1] == right - ): # ignore overlapping tokens - idx += 1 - - while ( - idx_std + 1 < len(offset_mapping_std) - and offset_mapping_std[idx_std + 1][1] == right_std - ): # ignore overlapping tokens - idx_std += 1 - - segment_count = 1 # keep count of segments traversed, - segment_count_std = 1 # to track one-to-many, many-to-one conditions - - while idx < len(offset_mapping) and idx_std < len(offset_mapping_std): - if right < right_std: - # Examples: [|] edge, [\] next edge, [.] split - # (45, 49) - # (45, 48) (48, 51) std - # | .| \ - # | | | - if segment_count == 1 and segment_count_std > 1: # prevent many-to-many - # | . | \ - # | | | | | - left = offset_mapping[idx][0] - left_std = offset_mapping_std[idx_std][0] - splits.setdefault(idx, []) - splits[idx] += [left_std - left] # server token, split depth - segment_count_std = 1 - continue - - idx += 1 - if idx < len(offset_mapping): - right = offset_mapping[idx][1] - segment_count += 1 - - while ( - idx + 1 < len(offset_mapping) and offset_mapping[idx + 1][1] == right - ): # ignore right-aligned overlapping tokens - idx += 1 - - elif right_std < right: - if segment_count_std == 1 and segment_count > 1: # prevent many-to-many - # Examples: [|] edge, [\] next edge, [.] split - # | | | | . | - # | | \ - - # (775, 778, 781, 788, 791) - # (775, 782, 785, 795) std - # | | |. . | | allow for multiple splits on a single token - # | | | | - left = offset_mapping[idx][0] - splits.setdefault(idx, []) - splits[idx] += [right_std - left] # server token, split depth - segment_count = 1 - segment_count_std = 0 - - idx_std += 1 - if idx_std < len(offset_mapping_std): - right_std = offset_mapping_std[idx_std][1] - segment_count_std += 1 - - while ( - idx_std + 1 < len(offset_mapping_std) - and offset_mapping_std[idx_std + 1][1] == right_std - ): # ignore right-aligned overlapping tokens - idx_std += 1 - - else: # right == right_std - idx += 1 - if idx < len(offset_mapping): - right = offset_mapping[idx][1] - segment_count = 1 - - idx_std += 1 - if idx_std < len(offset_mapping_std): - right_std = offset_mapping_std[idx_std][1] - segment_count_std = 1 - - while ( - idx + 1 < len(offset_mapping) and offset_mapping[idx + 1][1] == right - ): # ignore right-aligned overlapping tokens - idx += 1 - - while ( - idx_std + 1 < len(offset_mapping_std) - and offset_mapping_std[idx_std + 1][1] == right_std - ): # ignore right-aligned overlapping tokens - idx_std += 1 - - continue - - for idx in splits: - splits[idx] = tuple( - splits[idx] - ) # to enable hashable depths for split_map_cache keying - - return splits - - -def get_tokenizer_sequence_mappings( - offset_mapping: List[tuple], offset_mapping_std: List[tuple] -) -> List[tuple]: - r""" - Greedily determine the one-to-one, one-to-many, or many-to-one token alignments - between input-to-standard tokenizations. - Disallow many-to-many mappings, but allow for right-aligned overlapping tokens. - Args: - offset_mapping (:obj:`List[tuple]`, `required`): - Tokenizer offset mappings for a specific sequence [(left_0, right_0), (left_1, right_1), ...]. - offset_mapping_std (:obj:`List[tuple]`, `required`): - Standard tokenizer offset mappings for a specific sequence [(left_0, right_0), (left_1, right_1), ...] - - Returns: - mappings (:obj:`List[tuple]`, `required`): - List of mapping tuples: - [tuple( right_idx, right_idx_std, - segment_count_base, segment_count_std_base, - segment_count_overlap, segment_count_std_overlap), ...] - """ - mappings = [] - - idx = 0 # index of token segment (server tokenization) - idx_std = 0 # index of token segment (standard tokenization) - - right = offset_mapping[idx][1] # first right edge - right_std = offset_mapping_std[idx_std][1] # first std right edge - - segment_count = 1 # keep count of segments traversed, - segment_count_std = 1 # to track one-to-many, many-to-one conditions - segment_count_overlap = 0 # keep count of overlapping segments - segment_count_std_overlap = 0 - - while ( - idx + 1 < len(offset_mapping) and offset_mapping[idx + 1][1] == right - ): # ignore overlapping tokens - idx += 1 - segment_count_overlap += 1 - - while ( - idx_std + 1 < len(offset_mapping_std) - and offset_mapping_std[idx_std + 1][1] == right_std - ): # ignore overlapping tokens - idx_std += 1 - segment_count_std_overlap += 1 - - while idx < len(offset_mapping) and idx_std < len(offset_mapping_std): - if right < right_std: - if segment_count == 1 and segment_count_std > 1: - # Examples: [|] edge, [\] next edge, [.] split - # | . | \ - # | | | | | - print("Unaligned: Expected an aligned std edge.") - print( - "idx, idx_std, right, right_std, segment_count, segment_count_std" - ) - print(idx, idx_std, right, right_std, segment_count, segment_count_std) - - idx += 1 - if idx < len(offset_mapping): - right = offset_mapping[idx][1] - segment_count += 1 - - while ( - idx + 1 < len(offset_mapping) and offset_mapping[idx + 1][1] == right - ): # ignore overlapping tokens - idx += 1 - segment_count_overlap += 1 - - elif right_std < right: - if segment_count_std == 1 and segment_count > 1: - # Examples: [|] edge, [\] next edge, [.] split - # | | | | . | - # | | \ - print("Unaligned: Expected an aligned edge.") - print( - "idx, idx_std, right, right_std, segment_count, segment_count_std" - ) - print(idx, idx_std, right, right_std, segment_count, segment_count_std) - - idx_std += 1 - if idx_std < len(offset_mapping_std): - right_std = offset_mapping_std[idx_std][1] - segment_count_std += 1 - - while ( - idx_std + 1 < len(offset_mapping_std) - and offset_mapping_std[idx_std + 1][1] == right_std - ): # ignore overlapping tokens - idx_std += 1 - segment_count_std_overlap += 1 - - else: # right == right_std - mappings += [ - ( - idx, - idx_std, - segment_count, - segment_count_std, - segment_count_overlap, - segment_count_std_overlap, - ) - ] - - segment_count_overlap = 0 - segment_count_std_overlap = 0 - - idx += 1 - if idx < len(offset_mapping): - right = offset_mapping[idx][1] - segment_count = 1 - - idx_std += 1 - if idx_std < len(offset_mapping_std): - right_std = offset_mapping_std[idx_std][1] - segment_count_std = 1 - - while ( - idx + 1 < len(offset_mapping) and offset_mapping[idx + 1][1] == right - ): # ignore overlapping tokens - idx += 1 - segment_count_overlap += 1 - - while ( - idx_std + 1 < len(offset_mapping_std) - and offset_mapping_std[idx_std + 1][1] == right_std - ): # ignore overlapping tokens - idx_std += 1 - segment_count_std_overlap += 1 - continue - - mappings += [ - (len(offset_mapping), len(offset_mapping_std), 1, 1, 0, 0) - ] # validation segment - - return mappings - - -def get_tokenizer_depth_split_map( - tokenizer: PreTrainedTokenizerBase, depths: tuple -) -> List[Dict[str, torch.LongTensor]]: - r""" - Split individual token strings at specified depths, retokenize each resulting segment, - keep only the first token of each segment (if there is one). - Purpose is to provide targets for scattering probabilities when a single distribution requires a depth split. - Args: - tokenizer (:obj:`PreTrainedTokenizerBase`, `required`): - Tokenizer. - depths (:obj:`tuple`, `required`): - Tuple of depths at which tokens strings will be split. - - Returns: - split_map (:obj:`List[Dict[str, torch.LongTensor]]`, `required`): - """ - split_map = [] - - phrases = tokenizer.batch_decode( - range(tokenizer.vocab_len) - ) # list of variable len strings (one per token) - - # first part of the phrase up to distance characters - split_phrases = [[phrase[: depths[0]] for phrase in phrases]] - for i in range(len(depths) - 1): - # middle parts of the phrase from distance characters to end - split_phrases += [[phrase[depths[i] : depths[i + 1]] for phrase in phrases]] - # right part of the phrase from distance characters to end - split_phrases += [[phrase[depths[-1] :] for phrase in phrases]] - - for i, phrases in enumerate( - split_phrases - ): # loop through left, middle, right phrase collections - side_tokens = tokenizer(phrases)["input_ids"] # tokenize phrase collection - tokens_lens = [len(p) for p in side_tokens] # get token lengths of each phrase - from_idx = [ - i for i, l in enumerate(tokens_lens) if l > 0 - ] # only non-zero len tokens list - first_tokens = [ - side_tokens[i][0] for i in from_idx - ] # collect first tokens of each tokenized phrase - # add dict for phrase collection, mapping from original index to first tokens of tokenized phrase substrings - split_map += [ - { - "from": torch.tensor(from_idx, dtype=torch.long), - "to": torch.tensor(first_tokens, dtype=torch.long), - } - ] - - return split_map - - -def split_probs( - probs: torch.FloatTensor, split_map: List[Dict[str, torch.Tensor]] -) -> torch.FloatTensor: - r""" - Split a given probability distribution over a tokenizer vocabulary, given a split_map - of mappings from original tokens to target tokens at each depth of the split. - Args: - probs (:obj:`torch.FloatTensor`, `required`): - [vocab_size] Input probability distribution over a tokenizer vocabulary. - split_map (:obj:`List[Dict[str, torch.Tensor]]`, `required`): - A split_map of mappings from original tokens to target tokens at each depth of the split. - - Returns: - new_probs (:obj:`torch.FloatTensor`, `required`): - [splits, vocab_size] A new tensor with resultant probability distribution at each index - of the first dim, representing corresponding split depth. - """ - splits = len( - split_map - ) # how many parts to the depth split map, e.g. left, middle, right parts - vocab_size = probs.shape[0] # retain input vocabulary size - new_probs = torch.zeros((splits, vocab_size)).to( - probs.device - ) # provision prob dist for each part - - for pos in range(splits): # loop through all parts of the split - from_idx = split_map[pos]["from"] # from original string token index - to_idx = split_map[pos]["to"] # to first token index of retokenized part string - new_probs[pos].scatter_add_( - 0, to_idx, probs[from_idx] - ) # transfer probabilities to new part distributions - - return new_probs # [splits, vocab_size] - - -def align_tokenizer_sequences( - probs: torch.FloatTensor, - offset_mapping: List[tuple], - offset_mapping_std: List[tuple], - tokenizer: PreTrainedTokenizerBase, - split_map_cache: Dict[tuple, List[Dict[str, torch.Tensor]]], - tokens: torch.LongTensor, - tokens_std: torch.LongTensor, -) -> Tuple[torch.FloatTensor, List[tuple], torch.LongTensor]: - r""" - Align an input tokenization distribution to standard tokenization segments by depth-splitting - the input distribution at greedily chosen locations. Prepares the input distribution for mapping to a standard - distribution. - Args: - probs (:obj:`torch.FloatTensor`, `required`): - [sequence_len, vocab_size] Input probability distribution over a tokenizer vocabulary. - offset_mapping (:obj:`List[tuple]`, `required`): - Tokenizer offset mappings for a specific sequence [(left_0, right_0), (left_1, right_1), ...]. - offset_mapping_std (:obj:`List[tuple]`, `required`): - Standard tokenizer offset mappings for a specific sequence [(left_0, right_0), (left_1, right_1), ...] - tokenizer (:obj:`PreTrainedTokenizerBase`, `required`): - Source tokenizer. - split_map_cache (:obj:`Dict[tuple, List[Dict[str, torch.Tensor]]]`, `required`): - A dictionary of depths keying split_maps of mappings from original tokens to - target tokens at each depth of the split. - tokens (:obj:`torch.LongTensor`, `required`): - [sequence_len] A sequence of tokens produced by the source tokenizer. - tokens_std (:obj:`torch.LongTensor`, `required`): - [std_sequence_len] A sequence of tokens produced by the standard tokenizer. - - Returns: - aligned_probs (:obj:`torch.FloatTensor`, `required`): - [new_sequence_len, vocab_size] Aligned probability distribution over a tokenizer vocabulary. - aligned_offset_mapping (:obj:`List[tuple]`, `required`): - Tokenizer aligned offset mappings for a specific sequence [(left_0, right_0), (left_1, right_1), ...]. - aligned_tokens (:obj:`torch.LongTensor`, `required`): - A sequence of aligned tokens produced by the source tokenizer. - """ - aligned_tokens = [] # to store new aligned tokens - aligned_probs = [] # to store new aligned probability distributions - aligned_offset_mapping = ( - [] - ) # to store new aligned offset mappings of aligned tokens - splits = get_tokenizer_alignment_splits( - offset_mapping, offset_mapping_std - ) # get necessary token split locations - - prev_idx = 0 - for idx in splits: # each source token index that must be split - depths = splits[idx] # list of depths at which the token string must be split - aligned_probs += [probs[prev_idx:idx]] # retain preceding token probabilities - aligned_offset_mapping += offset_mapping[ - prev_idx:idx - ] # retain preceding offset mappings - aligned_tokens += [tokens[prev_idx:idx]] # retain preceding tokens - - if depths not in split_map_cache: - # add depths split to cache to reuse in future (split map calc is relatively time-consuming) - split_map_cache[depths] = get_tokenizer_depth_split_map(tokenizer, depths) - - new_probs = split_probs( - probs[idx], split_map_cache[depths] - ) # [splits, vocab_size] new split probabilities - aligned_probs += [new_probs] - - text_idx = tokenizer.decode(tokens[idx]) - - # === Left part === - new_tokens = tokenizer( - text_idx[: depths[0]], add_special_tokens=False, return_tensors="pt" - )["input_ids"][0] - aligned_tokens += [new_tokens[:1]] - aligned_offset_mapping += [ - (offset_mapping[idx][0], offset_mapping[idx][0] + depths[0]) - ] - - # === Middle parts === - for d in range(len(depths) - 1): - new_tokens = tokenizer( - text_idx[depths[d] : depths[d + 1]], - add_special_tokens=False, - return_tensors="pt", - )["input_ids"][0] - aligned_tokens += [new_tokens[:1]] - aligned_offset_mapping += [ - ( - offset_mapping[idx][0] + depths[d], - offset_mapping[idx][0] + depths[d + 1], - ) - ] - - # == Right part === - new_tokens = tokenizer( - text_idx[depths[-1] :], add_special_tokens=False, return_tensors="pt" - )["input_ids"][0] - aligned_tokens += [new_tokens[:1]] - aligned_offset_mapping += [ - (offset_mapping[idx][0] + depths[-1], offset_mapping[idx][1]) - ] - - prev_idx = idx + 1 - - aligned_probs += [probs[prev_idx:]] # retain remainder of tokens probabilities - aligned_tokens += [tokens[prev_idx:]] # retain remainder of tokens - aligned_offset_mapping += offset_mapping[ - prev_idx: - ] # retain remainder of offset mappings - - aligned_probs = torch.cat( - aligned_probs, dim=0 - ) # [sequence_len, vocab_size] assemble final probability tensor - aligned_tokens = torch.cat( - aligned_tokens, dim=0 - ).long() # [sequence_len] assemble final token sequence - - return aligned_probs, aligned_offset_mapping, aligned_tokens - - -def get_translation_map( - from_tokenizer: PreTrainedTokenizerBase, to_tokenizer: PreTrainedTokenizerBase -) -> Dict[str, Any]: - r""" - Map individual token phrases from a tokenizer to another tokenizer. - Args: - from_tokenizer (:obj:`PreTrainedTokenizerBase`, `required`): - From tokenizer. - to_tokenizer (:obj:`PreTrainedTokenizerBase`, `required`): - To tokenizer. - - Returns: - translation_map (:obj:`Dict[str, Any]`, `required`): - Maps for each observed length, a source token to a token sequence of that length, - with source index to target indices. - """ - set_vocab_len(from_tokenizer) - set_vocab_len(to_tokenizer) - - translation_map = {"lengths": {}} - - phrases = from_tokenizer.batch_decode( - range(from_tokenizer.vocab_len) - ) # tokens to strings - - to_tokens = to_tokenizer(phrases)[ - "input_ids" - ] # convert single token from-phrases to to-tokenization - to_tokens_lens = [len(p) for p in to_tokens] - unique_lens = set(to_tokens_lens) - max_len = max(unique_lens) - counts = torch.zeros((max_len, to_tokenizer.vocab_len), dtype=torch.long) - - for l in unique_lens: # each unique one-to-many mapping length - from_idx = [ - i for i, k in enumerate(to_tokens_lens) if k == l - ] # find len l to-tokenizations - subset = [to_tokens[i] for i in from_idx] # find len l to-tokenizations - from_idx = torch.tensor(from_idx, dtype=torch.long) # [subset_size] - to_idx = torch.tensor(subset, dtype=torch.long) # [subset_size, l] - translation_map["lengths"][l] = {"from": from_idx, "to": to_idx} - # accumulate counts on tokens, to be used to divide probability mass over its channeled sequences - counts[:l, :].scatter_add_( - 1, to_idx.T, torch.ones((l, len(subset)), dtype=torch.long) - ) - - translation_map["counts"] = counts - return translation_map - - -def translate_one_to_many( - probs_from: torch.FloatTensor, - probs_to: torch.FloatTensor, - translation_map: Dict[str, Any], -) -> None: - r""" - Translate a single token probability distribution from a source tokenization to a - sequence of probability distributions over a target tokenization. - Args: - probs_from (:obj:`torch.FloatTensor`, `required`): - [vocab_size] Input probability distribution over a from-tokenizer vocabulary. - probs_to (:obj:`torch.FloatTensor`, `required`): - [many, vocab_size] Output probability distributions over a to-tokenizer vocabulary. - translation_map (:obj:`Dict[str, Any]`, `required`): - Maps for each observed length, a source token to a token sequence of that length, - with source index to target indices. - - Returns: - - """ - many_len = probs_to.shape[0] - - # === Unroll single distribution into std sequence === - for i in range(many_len): # each unrolling step - for map_len in translation_map[ - "lengths" - ].keys(): # each one-to-many mapping length available - if map_len < i + 1: - continue # skip unrolling steps not available in a shorter mapping length - from_idx = translation_map["lengths"][map_len]["from"] - to_idx = translation_map["lengths"][map_len][ - "to" - ].T # [map_len, subset_size_std] - probs_to[i, :].scatter_add_( - 0, to_idx[i, :], probs_from[from_idx] - ) # add probs in-place - - -def translate_many_to_one( - probs_from: torch.FloatTensor, - probs_to: torch.FloatTensor, - translation_map: Dict[str, Any], -) -> None: - r""" - Translate a sequence of token probability distributions from a source tokenization to a - single token probability distribution over a target tokenization. - Args: - probs_from (:obj:`torch.FloatTensor`, `required`): - [many, vocab_size] Input probability distributions over a from-tokenizer vocabulary. - probs_to (:obj:`torch.FloatTensor`, `required`): - [vocab_size] Output probability distribution over a to-tokenizer vocabulary. - translation_map (:obj:`Dict[str, Any]`, `required`): - Maps for each observed length, a source token to a token sequence of that length, - with source index to target indices. - - Returns: - - """ - many_len = probs_from.shape[0] - probs_from_copy = probs_from.clone() # will modify from-probabilities - - # === Spread probability mass over realized sequences === - counts = translation_map["counts"] # [max_len, vocab_size] - translation_max_len = counts.shape[ - 0 - ] # maximum possible many-to-one length available in translation map - - if many_len <= translation_max_len: - probs_from_copy /= counts[ - :many_len, : - ] # divide probability mass by amount of paths crossing each token - else: # limit probs_from token depth to max_len - probs_from_copy[:translation_max_len, :] /= counts - - # === Reverse map std token to source sequences, gather avg. sequence prob === - for map_len in translation_map[ - "lengths" - ].keys(): # mutually exclusive over std tokens - from_idx = translation_map["lengths"][map_len][ - "from" - ] # [subset_size_std] one std token - to_idx = translation_map["lengths"][map_len][ - "to" - ].T # [map_len, subset_size_std] many server token seq - if many_len < map_len: # sequence beyond segment_count has min probability 0 - to_idx = to_idx[:many_len, :] # [segment_count, subset_size_std] - server_seq_tokens = probs_from_copy.gather( - 1, to_idx - ) # [map_len, subset_size_std] gather sequences - probs_to[from_idx] = ( - server_seq_tokens.sum(dim=0) / map_len - ) # [subset_size_std] in-place average approx. - - -def translate_tokenizer_probs( - probs: torch.FloatTensor, - probs_std: torch.FloatTensor, - offset_mapping: List[tuple], - offset_mapping_std: List[tuple], - tokenizer: PreTrainedTokenizerBase, - std_tokenizer: PreTrainedTokenizerBase, - split_map_cache: Dict[tuple, List[Dict[str, torch.Tensor]]], - to_translation_map: Dict[str, Any], - from_translation_map: Dict[str, Any], - tokens: torch.LongTensor, - tokens_std: torch.LongTensor, -) -> None: - r""" - Translates source token probability distributions to target probability distributions, by - aligning segments through source token splits, then greedily performing one-to-one, - one-to-many, many-to-one distribution mappings. - Args: - probs (:obj:`torch.FloatTensor`, `required`): - [sequence_len, vocab_size] Input probability distribution over a source tokenizer vocabulary. - probs_std (:obj:`torch.FloatTensor`, `required`): - [std_sequence_len, std_vocab_size] Output probability distribution over a target tokenizer vocabulary. - Reference that will be written in-place. - offset_mapping (:obj:`List[tuple]`, `required`): - Tokenizer offset mappings for a specific sequence [(left_0, right_0), (left_1, right_1), ...]. - offset_mapping_std (:obj:`List[tuple]`, `required`): - Standard tokenizer offset mappings for a specific sequence [(left_0, right_0), (left_1, right_1), ...] - tokenizer (:obj:`PreTrainedTokenizerBase`, `required`): - Source tokenizer. - std_tokenizer (:obj:`PreTrainedTokenizerBase`, `required`): - Standard/target tokenizer. - split_map_cache (:obj:`Dict[tuple, List[Dict[str, torch.Tensor]]]`, `required`): - A dictionary of depths keying split_maps of mappings from original tokens to - target tokens at each depth of the split. Adds split_maps to cache for faster future recall. - tokens (:obj:`torch.LongTensor`, `required`): - [sequence_len] A sequence of tokens produced by the source tokenizer. - tokens_std (:obj:`torch.LongTensor`, `required`): - [std_sequence_len] A sequence of tokens produced by the standard tokenizer. - to_translation_map (:obj:`Dict[str, Any]`, `required`): - Maps for each observed length, a source token to a token sequence of that length, - with source index to target indices. - from_translation_map (:obj:`Dict[str, Any]`, `required`): - Maps for each observed length, a source token to a token sequence of that length, - from target index to source indices. - - Returns: - - """ - # === Align tokenized sequences via source token splitting === - result = align_tokenizer_sequences( - probs, - offset_mapping, - offset_mapping_std, - tokenizer, - split_map_cache, - tokens.cpu(), - tokens_std.cpu(), - ) - aligned_probs, aligned_offset_mapping, aligned_tokens = result - - # === Get one-to-many / many-to-one mappings === - mappings = get_tokenizer_sequence_mappings( - aligned_offset_mapping, offset_mapping_std - ) - - # === Perform probability mappings === - for ( - right_idx, - right_idx_std, - segment_count_base, - segment_count_std_base, - segment_count_overlap, - segment_count_std_overlap, - ) in mappings[ - 1: - ]: # don't map start token - segment_count = ( - segment_count_base + segment_count_overlap - ) # calculate effective segments length - segment_count_std = ( - segment_count_std_base + segment_count_std_overlap - ) # calculate effective segments length - - # === One-to-many / one-to-one mapping === - if segment_count_base == 1: - start_idx_std = ( - right_idx_std - segment_count_std - ) # calculate starting index - - translate_one_to_many( - aligned_probs[right_idx - 1], - probs_std[start_idx_std : start_idx_std + segment_count_std], - to_translation_map, - ) - - # === Many-to-one mapping === - elif segment_count_std_base == 1: # many-to-one - start_idx = right_idx - segment_count # calculate starting index - - translate_many_to_one( - aligned_probs[start_idx:right_idx], - probs_std[right_idx_std - 1], - from_translation_map, - ) - - else: - print("Undefined mapping.") - - -def get_top_probs( - probs: torch.FloatTensor, tokenizer: PreTrainedTokenizerBase, amount: int = 10 -) -> str: - r""" - Constructs output string with top amount of highest probability token strings. - Used to display the top probabilities. - Args: - probs (:obj:`torch.FloatTensor`, `required`): - [vocab_size] Probability distribution over a tokenizer vocabulary. - tokenizer (:obj:`PreTrainedTokenizerBase`, `required`): - Tokenizer. - amount: (:obj:`int`, `optional`): - Amount of top tokens to return - - Returns: - string (:obj:`str`, `required`): - Highest probability token strings, prob[token-string] ... - """ - string = "" - - vals, indices = probs.sort( - dim=-1, descending=True - ) # descending sort token probabilities - - for i in range(amount): - string += "%.4f[%s] " % ( - vals[i], - tokenizer.decode(indices[i]), - ) # prob[token-string] - - return string - - -def translate_logits_to_probs_std( - logits: torch.FloatTensor, - offset_mapping: List[List[tuple]], - offset_mapping_std: List[List[tuple]], - tokenizer: PreTrainedTokenizerBase, - std_tokenizer: PreTrainedTokenizerBase, - split_map_cache: Dict[tuple, List[Dict[str, torch.Tensor]]], - to_translation_map: Dict[str, Any], - from_translation_map: Dict[str, Any], - tokens: torch.LongTensor, - tokens_std: torch.LongTensor, - skip_equivalent: bool = True, -) -> torch.FloatTensor: - r""" - Translates source token logit scores to probability distributions over the standard tokenizer. - Args: - logits (:obj:`torch.FloatTensor`, `required`): - [batch_size, sequence_len, vocab_size] Input source logits over a source tokenizer vocabulary. - offset_mapping (:obj:`List[List[tuple]]`, `required`): - Batch of tokenizer offset mappings - [[(left_0, right_0), (left_1, right_1), ...], ...]. - offset_mapping_std (:obj:`List[List[tuple]]`, `required`): - Batch of standard tokenizer offset mappings - [[(left_0, right_0), (left_1, right_1), ...], ...]. - tokenizer (:obj:`PreTrainedTokenizerBase`, `required`): - Source tokenizer. - std_tokenizer (:obj:`PreTrainedTokenizerBase`, `required`): - Standard/target tokenizer. - split_map_cache (:obj:`Dict[tuple, List[Dict[str, torch.Tensor]]]`, `required`): - A dictionary of depths keying split_maps of mappings from original tokens to - target tokens at each depth of the split. Adds split_maps to cache for faster future recall. - tokens (:obj:`torch.LongTensor`, `required`): - [batch_size, sequence_len] A sequence of tokens produced by the source tokenizer. - tokens_std (:obj:`torch.LongTensor`, `required`): - [batch_size, std_sequence_len] A sequence of tokens produced by the standard tokenizer. - to_translation_map (:obj:`Dict[str, Any]`, `required`): - Maps for each observed length, a source token to a token sequence of that length, - with source index to target indices. - from_translation_map (:obj:`Dict[str, Any]`, `required`): - Maps for each observed length, a source token to a token sequence of that length, - from target index to source indices. - skip_equivalent (:obj:`bool`, `optional`): - Skips translation if tokenizer and std_tokenizer are equivalent. - - Returns: - probs_std (:obj:`torch.FloatTensor`, `required`): - [batch_size, std_sequence_len, std_vocab_size] Output probability distribution over the - standard tokenizer vocabulary. - """ - set_vocab_len(tokenizer) - set_vocab_len(std_tokenizer) - - # === Check tokenizer equivalence / Skip if equivalent === - if skip_equivalent and check_tokenizer_equivalence(tokenizer, std_tokenizer): - logits = logits.to(torch.float).to("cpu") - probs = torch.softmax(logits, dim=2) - return probs - - # === Get shape sizes === - batch_size, sequence_len, vocab_size = logits.shape - std_sequence_len = tokens_std.shape[-1] - std_vocab_size = std_tokenizer.vocab_len - - if tokenizer.vocab_len < vocab_size: - logits = logits[..., : tokenizer.vocab_len] - vocab_size = tokenizer.vocab_len - - # === Convert logits to probabilities === - logits = logits.to(torch.float).to("cpu") - probs = torch.softmax(logits, dim=2) # [batch_size, sequence_len, vocab_size] - - if ( - vocab_size < tokenizer.vocab_len - ): # fixes bug when model logits output is not full width - padded_probs = torch.zeros((batch_size, sequence_len, tokenizer.vocab_len)) - padded_probs[..., :vocab_size] = probs - probs = padded_probs - - # === Translate to probabilities over standard tokenizer === - probs_std = torch.zeros(batch_size, std_sequence_len, std_vocab_size) - for b in range(batch_size): - probs_b = probs[b][-len(offset_mapping[b]) :] # remove left padding - tokens_b = tokens[b][-len(offset_mapping[b]) :] # remove left padding - translate_tokenizer_probs( - probs_b, - probs_std[b], - offset_mapping[b], - offset_mapping_std[b], - tokenizer, - std_tokenizer, - split_map_cache, - to_translation_map, - from_translation_map, - tokens_b, - tokens_std[b], - ) - - # === Correct excess probability mass (haircut) === - probs_std_sum = probs_std.sum(dim=-1) # [batch_size, std_sequence_len] - over = probs_std_sum > 1 - probs_std[over] /= probs_std_sum[over][:, None] - - # === Correct deficient probability mass (raise) === - probs_std_sum = probs_std.sum(dim=-1) # [batch_size, std_sequence_len] - under = probs_std_sum < 1 - probs_std[under] += ((1 - probs_std_sum[under]) / probs_std[under].shape[-1])[ - :, None - ] # raise noise floor so sum 1 - - return probs_std # [batch_size, std_sequence_len, std_vocab_size] - - -def topk_token_phrases( - logits: torch.Tensor, - tokenizer: PreTrainedTokenizerBase, - topk: int, - ignore_index: int = -100, -) -> torch.Tensor: - r""" - Select topk tokenizer logits/phrases and include std_token_phrases counterparts (std_tokenization of token text) - in topk_tensor output of shape [batch_size, (topk + 1), max_len], where max len of all phrase lists - (with prob in front) is max_{b,k}(len([prob_k, tok_0_k, tok_1_k, ...])). - The output topk_tensor also includes a floor_prob for each batch item. The floor probability is the - mean probability of token phrases not captured in topk, required since the tokenizer vocab_size may - not be known to the receiver. - Requires prep_tokenizer(tokenizer, std_tokenizer) to set_std_token_phrases first, to make - std_token_phrases available here. - Args: - logits (:obj:`torch.Tensor`, `required`): - [batch_size, vocab_size] Input source logits for last token over a source tokenizer vocabulary. - tokenizer (:obj:`PreTrainedTokenizerBase`, `required`): - Source tokenizer (usually server tokenizer) - topk (:obj:`int`, `required`): - Amount of top phrases to expect (to check for mismatch) - ignore_index (:obj:`int`, `optional`): - Padding value to use for unfilled token positions in a shorter token phrase. - - Returns: - topk_tensor (:obj:`torch.Tensor`, `required`): - [batch_size, (topk + 1), max_len] tensor includes topk token probabilities (prob_k) + floor_prob - in first column with gradients attached, with std_tokens in remaining columns with ignore_index padding. - Content structure: - [[[prob_k=0_b=0, tok_0_k=0_b=0, tok_1_k=0_b=0, ..., ignore_index?], - [prob_k=1_b=0, tok_0_k=1_b=0, tok_1_k=1_b=0, ..., ignore_index?], - [...], - [prob_floor_b=0, ignore_index, ..., ignore_index]], - [[prob_k=0_b=1, tok_0_k=0_b=1, tok_1_k=0_b=1, ..., ignore_index?], - [prob_k=1_b=1, tok_0_k=1_b=1, tok_1_k=1_b=1, ..., ignore_index?], - [...], - [prob_floor_b=1, ignore_index, ..., ignore_index]], - [...]] - """ - # Get shape sizes - ( - batch_size, - vocab_size, - ) = logits.shape # [batch_size, vocab_size] only last token prediction - - # Convert logits to probabilities - logits = ( - logits.float() - ) # ensure further computations done in float32 for improved precision - probs = torch.softmax(logits, dim=1) # [batch_size, vocab_size] - - # TopK phrase selection - topk_probs, topk_indices = torch.topk( - probs, topk - ) # topk probs and indices: [batch_size, topk] - - # === Calculate floor probability === - topk_pmass = topk_probs.sum(dim=-1) # [batch_size] topk probability mass - remainder_pmass = torch.clamp( - 1 - topk_pmass, 1e-40, 1 - ) # [batch_size] remainder probability mass - floor_probs = remainder_pmass / (vocab_size - topk) # [batch_size]divide remainder - - # convert to list for faster iteration in list comprehension - topk_probs_list = topk_probs.tolist() - topk_indices_list = topk_indices.tolist() - floor_probs_list = floor_probs.tolist() - - # === Construct topk phrases list === - probs = ( - [] - ) # collect probability tensors with gradients attached (to be grafted into topk_tensor) - phrases = ( - [] - ) # form topk token phrases with prob prepend [prob, tok_0, tok_1, ... tok_n] - - for b in range(batch_size): - # collect probability tensors with gradients attached (to be grafted into topk_tensor) - probs += [ - topk_probs[b], - floor_probs[b], - ] # [tensor(prob_k=0_b, prob_k=1_b, ...), tensor(prob_floor_b)] - - # form topk token phrases with prob prepend [prob, tok_0, tok_1, ... tok_n] - phrases += [ - [prob] + tokenizer.std_token_phrases[i] - for prob, i in zip(topk_probs_list[b], topk_indices_list[b]) - ] # [prob_k, tok_0_k, tok_1_k, ...] - - # also add prob_floor for batch item - phrases += [[floor_probs_list[b]]] # [prob_floor_b] - - # determine width of topk_tensor as max len of all phrase lists (with prob in front) - max_len = max( - [len(p) for p in phrases] - ) # max_{b,k}(len([prob_k, tok_0_k, tok_1_k, ...])) - - # form single 2D tensor with all phrase and probs (typically to send to axon wire encoding) - topk_tensor = torch.tensor( - [p + [ignore_index] * (max_len - len(p)) for p in phrases] - ).to( - logits.device - ) # [batch_size * (topk + 1), max_len] - - # grafting probability tensors into first column to attach gradients - topk_tensor[:, 0] = torch.hstack( - probs - ) # tensor([prob_k=0_b, prob_k=1_b, ..., prob_floor_b]) - - topk_tensor = topk_tensor.reshape( - batch_size, topk + 1, max_len - ) # [batch_size, (topk + 1), max_len] reshaped - - return topk_tensor # [batch_size, (topk + 1), max_len] (probability gradients attached in first column) - - -def compact_topk_token_phrases(topk_tensor: torch.Tensor): - r""" - Compact 2D topk_tensor [batch_size, (topk + 1), max_len] by removing ignore_index padding, and also offset - tokens by 2 to preserve [0, 1] for probabilities to allow for proper unraveling demarcated by - probability boundaries. - Args: - topk_tensor (:obj:`torch.Tensor`, `required`): - [batch_size, (topk + 1), max_len] tensor includes topk token probabilities (prob_k) + floor_prob - in first column with gradients attached, with std_tokens in remaining columns with ignore_index padding. - Content structure: - [[[prob_k=0_b=0, tok_0_k=0_b=0, tok_1_k=0_b=0, ..., ignore_index?], - [prob_k=1_b=0, tok_0_k=1_b=0, tok_1_k=1_b=0, ..., ignore_index?], - [...], - [prob_floor_b=0, ignore_index, ..., ignore_index]], - [[prob_k=0_b=1, tok_0_k=0_b=1, tok_1_k=0_b=1, ..., ignore_index?], - [prob_k=1_b=1, tok_0_k=1_b=1, tok_1_k=1_b=1, ..., ignore_index?], - [...], - [prob_floor_b=1, ignore_index, ..., ignore_index]], - [...]] - - Returns: - compact_topk (:obj:`torch.Tensor`, `required`): - [sum_b(sum_k(len(phrase_k) + 1)_b)] Compacted 1-D tensor >= batch_size * (2 * topk + 1), - since 2 * topk + 1: topk x [probability, token sequence (at least one token)] + - floor probability (rest). - Content structure: - [prob_k=0_b=0, tok_0_k=0_b=0, tok_1_k=0_b=0, ..., prob_k=1_b=0, tok_0_k=1_b=0, ..., prob_floor_b=0, - prob_k=0_b=1, tok_0_k=0_b=1, tok_1_k=0_b=1, ..., prob_k=1_b=1, tok_0_k=1_b=1, ..., prob_floor_b=1, - ...] - """ - topk_tensor_offset = ( - topk_tensor.clone() - ) # assume topk_tensor may be reused elsewhere so clone - topk_tensor_offset[ - :, :, 1: - ] += 2 # add 2 to token ids to preserve [0, 1] for probabilities (in first column) - - flattened = ( - topk_tensor_offset.flatten() - ) # [batch_size * (topk + 1) * max_len] 1D tensor - compact_topk = flattened[ - flattened > -1 - ] # remove ignore_index < -1 padding to compact content - - return compact_topk # [>= batch_size * (2 * topk + 1)] - - -def unravel_topk_token_phrases( - compact_topk: torch.Tensor, topk: int, ignore_index: int = -100 -) -> torch.Tensor: - r""" - Unravel topk token phrases input_tensor from 1-D to [batch_size, (topk + 1), max_len] topk_tensor, which - includes topk token probabilities (prob_k) + floor_prob in first column with gradients attached, with - std_tokens in remaining columns with ignore_index padding. - Args: - compact_topk (:obj:`torch.Tensor`, `required`): - [sum_b(sum_k(len(phrase_k) + 1)_b)] Compacted 1-D tensor >= batch_size * (2 * topk + 1), - since 2 * topk + 1: topk x [probability, token sequence (at least one token)] + - floor probability (rest). - Content structure: - [prob_k=0_b=0, tok_0_k=0_b=0, tok_1_k=0_b=0, ..., prob_k=1_b=0, tok_0_k=1_b=0, ..., prob_floor_b=0, - prob_k=0_b=1, tok_0_k=0_b=1, tok_1_k=0_b=1, ..., prob_k=1_b=1, tok_0_k=1_b=1, ..., prob_floor_b=1, - ...] - topk (:obj:`int`, `required`): - Amount of top phrases to expect (to check for mismatch) - ignore_index (:obj:`int`, `optional`): - Padding value to use for unfilled token positions in a shorter token phrase. - Returns: - topk_tensor (:obj:`torch.Tensor`, `required`): - [batch_size, (topk + 1), max_len] tensor includes topk token probabilities (prob_k) + floor_prob - in first column with gradients attached, with std_tokens in remaining columns with ignore_index padding. - Content structure: - [[[prob_k=0_b=0, tok_0_k=0_b=0, tok_1_k=0_b=0, ..., ignore_index?], - [prob_k=1_b=0, tok_0_k=1_b=0, tok_1_k=1_b=0, ..., ignore_index?], - [...], - [prob_floor_b=0, ignore_index, ..., ignore_index]], - [[prob_k=0_b=1, tok_0_k=0_b=1, tok_1_k=0_b=1, ..., ignore_index?], - [prob_k=1_b=1, tok_0_k=1_b=1, tok_1_k=1_b=1, ..., ignore_index?], - [...], - [prob_floor_b=1, ignore_index, ..., ignore_index]], - [...]] - """ - - atol = 1e-6 # absolute tolerance - # Find probability markers (per batch item: topk phrase probabilities + floor_prob) - prob_idx = torch.where((-atol < compact_topk) & (compact_topk < 1 + atol))[ - 0 - ] # 0 <= prob <= 1 [batch_size * (topk + 1)], expect token_ids >= 2 - - batch_size = len(prob_idx) // ( - topk + 1 - ) # (batch_size * (topk + floor)) / (topk + floor) - assert batch_size * (topk + 1) == len(prob_idx), ( - f"unravel_topk_token_phrases() probability marker failure: " - f"{batch_size} * ({topk} + 1) != {len(prob_idx)}" - ) # decoding irregularity otherwise - - probs = torch.clamp( - compact_topk[prob_idx], 0, 1 - ) # [batch_size * (topk + 1)] ensure probabilities within [0, 1] - probs_sum = probs.reshape(batch_size, topk + 1).sum(dim=1) # [batch_size] - assert torch.all( - (-atol < probs_sum) & (probs_sum < 1 + atol) - ), f"unravel_topk_token_phrases(): probs_sum not in [0, 1]" - - # Obtain phrase lengths and maximum phrase length - phrase_len = ( - prob_idx[1:] - prob_idx[:-1] - ) # [batch_size * (topk + 1) - 1] length of each phrase - phrase_len = torch.cat( - (phrase_len, torch.tensor([1])) - ) # [batch_size * (topk + 1)] prob_floor is always len=1 - max_len = ( - phrase_len.max() - ) # determine width of topk_tensor as max len of all phrase lists (with prob in front) - - # Initialize topk_tensor with ignore_index + 2, since decrement with 2 follows to remove token offset later - topk_tensor = torch.ones( - (batch_size * (topk + 1), max_len), device=compact_topk.device - ) - topk_tensor *= ignore_index + 2 # [batch_size * (topk + 1), max_len] - - # Insert phrases of each unique length as block into topk_tensor - for unique_len in phrase_len.unique(): - if unique_len <= 1: - continue # skip probability column, will be added afterward - - phrase_idx = torch.where(phrase_len == unique_len)[ - 0 - ] # phrase indices where phrase_len is unique_len - compact_idx = prob_idx[phrase_idx] # indices in compact_topk - - # Create indexing block, add index for each phrase position, skip first (prob) position - block_idx = [ - compact_idx + position for position in range(1, unique_len) - ] # incrementally add each position of phrase - # transpose .t() ensures correct interleaving of consecutive positions: - # [[phrase_a_1, phrase_a_2, ..., phrase_a_n], [phrase_b_1, phrase_b_2, ..., phrase_b_n], ...] - block_idx = ( - torch.vstack(block_idx).t().reshape(-1, unique_len - 1) - ) # [-1, unique_len - 1] for all phrases with unique_len - - topk_tensor[phrase_idx, 1:unique_len] = compact_topk[ - block_idx - ] # slice selected phrases and copy into topk_tensor - - topk_tensor -= 2 # remove token offset, overwrites probability column, replace probabilities below - - # grafting probability tensors into first column to attach gradients - topk_tensor[:, 0] = probs # tensor([prob_k=0_b, prob_k=1_b, ..., prob_floor_b]) - - topk_tensor = topk_tensor.reshape( - batch_size, topk + 1, max_len - ) # [batch_size, (topk + 1), max_len] reshaped - - return topk_tensor # [batch_size, (topk + 1), max_len] - - -def phrase_cross_entropy( - target_phrases: Union[List[List[int]], torch.Tensor], - topk_tensor: torch.Tensor, - ignore_index: int = -100, - reduce=True, - reduction="mean", - vocab_size_min: int = 50257, -) -> Tuple[torch.Tensor, torch.Tensor]: - r""" - Calculates the cross entropy of a phrase prediction against a target phrase, so that this is a multi-token - extension of typical cross entropy calculated for next token prediction. - Args: - target_phrases (:obj:`List[List[int]]`, `required`): - [batch_size, *] Target phrases in standard token sequence list. - topk_tensor (:obj:`torch.Tensor`, `required`): - [batch_size, (topk + 1), max_len] tensor includes topk token probabilities (prob_k) + floor_prob - in first column with gradients attached, with std_tokens in remaining columns with ignore_index padding. - Content structure: - [[[prob_k=0_b=0, tok_0_k=0_b=0, tok_1_k=0_b=0, ..., ignore_index?], - [prob_k=1_b=0, tok_0_k=1_b=0, tok_1_k=1_b=0, ..., ignore_index?], - [...], - [prob_floor_b=0, ignore_index, ..., ignore_index]], - [[prob_k=0_b=1, tok_0_k=0_b=1, tok_1_k=0_b=1, ..., ignore_index?], - [prob_k=1_b=1, tok_0_k=1_b=1, tok_1_k=1_b=1, ..., ignore_index?], - [...], - [prob_floor_b=1, ignore_index, ..., ignore_index]], - [...]] - ignore_index (:obj:`int`, `optional`): - Padding value to use for unfilled token positions in a shorter token phrase. - reduce (:obj:`bool`, `optional`): - Whether to reduce the cross entropy over the batch dimension. - reduction (:obj:`str`, `optional`): - Reduction function to perform when reduce is True. - vocab_size_min (:obj:`int`, `optional`): - Minimum server vocab_size expected, should set to nominal 50257, - used to prevent the floor_probs from being too large. - Returns: - loss_val (:obj:`torch.Tensor`, `required`): - Validation cross entropy loss, either scalar if reduce or [batch_size]. - loss (:obj:`torch.Tensor`, `required`): - Phrase cross entropy loss, either scalar if reduce or [batch_size]. - """ - - ( - batch_size, - topk_p1, - max_len, - ) = topk_tensor.shape # [batch_size, (topk + 1), max_len] - topk = topk_p1 - 1 - - topk_tokens = ( - topk_tensor[:, :-1, 1:].round().int() - ) # [batch_size, topk, max_len - 1] Phrase tokens with ignore_index token for padding. - topk_probs = topk_tensor[ - :, :-1, 0 - ] # [batch_size, topk] Probabilities for each phrase in topk - floor_probs = topk_tensor[ - :, -1, 0 - ] # [batch_size] Floor probabilities as mean probability for non-topk tokens - - topk_probs = torch.clamp( - topk_probs, 0, 1 - ) # [batch_size, topk] ensure probabilities within [0, 1] - floor_probs = torch.clamp( - floor_probs, 0, 1 - ) # [batch_size] ensure floor probabilities within [0, 1] - - # === Ensure total probability is 1 === - total_probs = ( - topk_probs.sum(dim=-1) + max(0, vocab_size_min - topk) * floor_probs - ) # [batch_size] total probs - n_topk_probs = ( - topk_probs / total_probs[:, None] - ) # [batch_size, topk] normalized topk_probs - n_floor_probs = floor_probs / total_probs # [batch_size] normalized floor_probs - - val_probs = torch.zeros(batch_size).to( - topk_probs.device - ) # accumulate probabilities when first tokens match - match_probs = torch.zeros(batch_size).to( - topk_probs.device - ) # accumulate probabilities when sub target matches phrase - for b in range(batch_size): - target_phrase = target_phrases[b] - if not isinstance(target_phrase, torch.Tensor): - target_phrase = torch.tensor(target_phrases[b]) - if isinstance(target_phrase, torch.FloatTensor): - target_phrase = target_phrase.round().int() - - match = ( - topk_tokens[b, :, 0] == target_phrase[0].item() - ) # bool where first tokens match (validation token) - if match.sum() > 0: - val_probs[b] = n_topk_probs[b, match].sum() # accumulate all matches - else: # no matches - val_probs[b] = n_floor_probs[ - b - ] # assume match is in non-topk tokens with avg floor_prob - - # === Integrate sub target matches === - check_len = min(max_len - 1, len(target_phrase)) - for c in range(1, check_len + 1): # progressively increase sub target length - target = ignore_index * torch.ones(check_len, dtype=torch.int32).to( - topk_tensor.device - ) # [-100, ..., -100] - target[:c] = target_phrase[:c] # [tok0, tok1, ...tokc, -100, ..., -100] - - # Find sub target matches - match = topk_tokens[b, :, :check_len] == target - match_idx = torch.where(match.sum(dim=-1) == check_len)[ - 0 - ] # phrase indices which match sub target - - if len(match_idx): # at least one match - match_probs[b] += n_topk_probs[ - b, match_idx - ].sum() # accumulate all matches - else: # no matches - match_probs[b] += n_floor_probs[ - b - ] # assume match is in non-topk tokens with avg floor_prob - - val_probs = torch.clamp( - val_probs, 0, 1 - ) # [batch_size] ensure 0 <= total probability <= 1 - loss_val = -torch.log( - val_probs + 1e-40 - ) # [batch_size] calculate cross entropy loss - - match_probs = torch.clamp( - match_probs, 0, 1 - ) # [batch_size] ensure 0 <= total probability <= 1 - loss = -torch.log(match_probs + 1e-40) # [batch_size] calculate cross entropy loss - - if reduce: - if not hasattr(loss_val, reduction) or not hasattr(loss, reduction): - raise RuntimeError( - f"phase_cross_entropy(): Reduction function {reduction} not found." - ) - loss_val = getattr(loss_val, reduction)() - loss = getattr(loss, reduction)() - if loss.numel() > 1: - raise ValueError( - f"phase_cross_entropy(): Expected reduction to scalar, obtained {loss.shape} instead." - ) - - return loss_val, loss - - -def topk_tokens_to_vocab_size( - topk_tensor: torch.Tensor, vocab_size_std: int, vocab_size_min: int = 50257 -) -> torch.Tensor: - r""" - Convert topk_tokens first token probabilities into a standard logits tensor shape [batch_size, vocab_size_std]. - Args: - topk_tensor (:obj:`torch.Tensor`, `required`): - [batch_size, (topk + 1), max_len] tensor includes topk token probabilities (prob_k) + floor_prob - in first column with gradients attached, with std_tokens in remaining columns with ignore_index padding. - Content structure: - [[[prob_k=0_b=0, tok_0_k=0_b=0, tok_1_k=0_b=0, ..., ignore_index?], - [prob_k=1_b=0, tok_0_k=1_b=0, tok_1_k=1_b=0, ..., ignore_index?], - [...], - [prob_floor_b=0, ignore_index, ..., ignore_index]], - [[prob_k=0_b=1, tok_0_k=0_b=1, tok_1_k=0_b=1, ..., ignore_index?], - [prob_k=1_b=1, tok_0_k=1_b=1, tok_1_k=1_b=1, ..., ignore_index?], - [...], - [prob_floor_b=1, ignore_index, ..., ignore_index]], - [...]] - vocab_size_std (:obj:`int`, `optional`): - Standard tokenizer vocab_size for forming logits. - vocab_size_min (:obj:`int`, `optional`): - Minimum server vocab_size expected, should set to nominal 50257, - used to prevent the floor_probs from being too large. - Returns: - logits (:obj:`torch.Tensor`, `required`): - [batch_size, vocab_size_std] Standard logits. - """ - - ( - batch_size, - topk_p1, - max_len, - ) = topk_tensor.shape # [batch_size, (topk + 1), max_len] - topk = topk_p1 - 1 - - topk_tokens = ( - topk_tensor[:, :-1, 1].round().to(torch.int64) - ) # [batch_size, topk] first tokens - topk_probs = topk_tensor[ - :, :-1, 0 - ] # [batch_size, topk] Probabilities for each phrase in topk - floor_probs = topk_tensor[ - :, -1, 0 - ] # [batch_size] Floor probabilities as mean probability for non-topk tokens - - topk_probs = torch.clamp( - topk_probs, 0, 1 - ) # [batch_size, topk] ensure probabilities within [0, 1] - floor_probs = torch.clamp( - floor_probs, 0, 1 - ) # [batch_size] ensure floor probabilities within [0, 1] - - # === Ensure total probability is 1 === - total_probs = ( - topk_probs.sum(dim=-1) + max(0, vocab_size_min - topk) * floor_probs - ) # [batch_size] total probs - n_topk_probs = ( - topk_probs / total_probs[:, None] - ) # [batch_size, topk] normalized topk_probs - - # === Convert to logits tensor === - probs = torch.zeros((batch_size, vocab_size_std)) # [batch_size, vocab_size_std] - probs.scatter_add_( - 1, topk_tokens, n_topk_probs - ) # accumulate token probabilities onto logits tensor - - return probs # [batch_size, vocab_size_std] - - -def check_tokenizer_equivalence( - tokenizer_to_check: PreTrainedTokenizerBase, - target_tokenizer: PreTrainedTokenizerBase, -) -> bool: - r""" - Is tokenizer_to_check equivalent to target_tokenizer? - Args: - tokenizer_to_check (:obj:`PreTrainedTokenizerBase`, `required`): - Tokenizer to check for equivalence. - target_tokenizer (:obj:`PreTrainedTokenizerBase`, `required`): - Target tokenizer to check equivalence against. - - Returns: - result (:obj:`bool`, `required`) - """ - set_vocab_len(tokenizer_to_check) - set_vocab_len(target_tokenizer) - - if tokenizer_to_check.vocab_len != target_tokenizer.vocab_len: - return False - - to_check_vocab = tokenizer_to_check.batch_decode( - range(tokenizer_to_check.vocab_len) - ) - target_vocab = target_tokenizer.batch_decode(range(target_tokenizer.vocab_len)) - - return to_check_vocab == target_vocab # indexed tokenizer vocabularies should match - - -def prune_tokens(inputs: torch.FloatTensor, prune_len: int = 1, margin: int = 3): - r""" - Prune tokens from a batch of sequences randomly by removing prune_len tokens from each sequence, - leaving the end margin intact. - Args: - inputs (:obj:`torch.FloatTensor` of shape :obj:`(batch_size, seq_len)`, `required`): - Tensor inputs to have tokens pruned. - prune_len (:obj:`int`, `optional`): - Number of tokens to prune from each validation input sequence. - margin (:obj:`int`, `optional`): - Number of tokens at the end of the sequence to leave unpruned. - Returns: - pruned_inputs (:obj:`torch.FloatTensor` of shape :obj:`(batch_size, seq_len - prune_len)`, `required`) - """ - seq_len = len(inputs[0]) - if prune_len <= 0: - return inputs - elif seq_len - margin < prune_len: - prune_len = seq_len - margin - pruned_inputs = [] - for b in range(len(inputs)): - rand_index = torch.randperm(seq_len - margin)[:prune_len] - mask = torch.ones(seq_len, dtype=torch.bool) - mask[rand_index] = False - pruned_inputs.append(inputs[b, mask]) - - return torch.stack(pruned_inputs) - - -def pad_offsets( - offsets_batch: List[List[tuple]], - source_offsets_batch: List[List[List[Any]]], - pad_offsets_batch: List[List[List[Any]]], -) -> List[List[List[Any]]]: - r""" - Pads specific tuples in offsets_batch, selected by source_offsets_batch with - associated paddings in pad_offsets_batch. - Purpose is typically to add padding to align two tokenization offsets at special tokens. - Args: - offsets_batch (:obj:`List[List[tuple]]`, `required`): - Batch of full input tokenizer offset mappings to be used for alteration - [[(left_0, right_0), (left_1, right_1), ...], ...]. - source_offsets_batch (:obj:`List[List[List[Any]]]`, `required`): - Batch of tokenizer offset mappings indicating replacement tuples in offsets_batch - [[(left_0, right_0), (left_1, right_1), ...], ...]. - pad_offsets_batch (:obj:`List[List[List[Any]]]`, `required`): - Batch of offset paddings associated with each source_offsets_batch replacement tuple - [[(left_pad_0, right_pad_0), (left_pad_1, right_pad_1), ...], ...]. - - Returns: - new_offsets_batch (:obj:`List[List[List[Any]]]`, `required`): - Batch of padded full input tokenizer offset mappings - [[(left_0, right_0), (left_1, right_1), ...], ...]. - """ - new_offsets_batch = [] - batch_len = len(offsets_batch) - - for b in range(batch_len): - new_offsets = [] - pad = 0 - - idx = 0 - for left, right in offsets_batch[b]: # go through original offsets - if idx < len(source_offsets_batch[b]): - source_left, source_right = source_offsets_batch[b][idx] - if ( - left == source_left and right == source_right - ): # matching offset found - pad_left, pad_right = pad_offsets_batch[b][idx] - new_offsets += [ - (pad_left + pad, pad_right + pad) - ] # replace offsets with padded + accum. pad - pad += pad_right - right - idx += 1 - continue - new_offsets += [ - (left + pad, right + pad) - ] # adjust original offsets w/ accum. pad - - new_offsets_batch += [new_offsets] - - return new_offsets_batch - - -def find_offsets(string: str, substring: str) -> List[List[int]]: - r""" - Finds all the [start, end] offsets of substring in string. - Assumes there is no overlap of substring, nor recursive overlap. - Args: - string (:obj:`str`, `required`): - Main string to find offsets in. - substring (:obj:`str`, `required`): - Substring to search for in string. - - Returns: - offsets (:obj:`List[List[int]]`, `required`): - Offsets denoting the [start, end] positions of substring in string. - """ - offsets = [] - idx = string.find(substring) # find first instance - while idx != -1: # found an instance - offsets += [[idx, idx + len(substring)]] # add offsets - idx = string.find(substring, idx + len(substring)) # find next instance - - return offsets - - -def replace_at_offsets( - string: str, offsets: List[List[Any]] -) -> Tuple[str, List[List[int]]]: - r""" - Replace indicated [left, right] offset positions with a new substring, by - deleting [left, right] content and adding [left, left+len(substring)] substring, - adjusting offsets incrementally. - Assumes an incremental ordered, non-overlapping list of offsets, constructing - the new string incrementally and recording new offsets. - Args: - string (:obj:`str`, `required`): - Main string to perform replacements for. - offsets (:obj:`List[List[Any]]`, `required`): - Offsets where replacements are made with replacement substring - [[left_0, right_0, substring_0], ...] - - Returns: - new_string (:obj:`str`, `required`): - New string where replacements were made. - new_offsets (:obj:`List[List[Any]]`, `required`): - New offsets where replacements are now located - [[left_0, right_0], [left_1, right_1], ...] - """ - new_string = "" - new_offsets = [] - - prev = 0 - for left, right, substring in offsets: - new_string += string[prev:left] # retain preceding string - new_left = len(new_string) # advance index - - new_string += substring # add new substring - new_right = len(new_string) - - new_offsets += [[new_left, new_right]] # add offsets - - prev = right # advance index - - new_string += string[prev:] - - return new_string, new_offsets - - -def get_special_token_pairings( - from_tokenizer: PreTrainedTokenizerBase, to_tokenizer: PreTrainedTokenizerBase -) -> Dict[str, str]: - r""" - Determines a prioritized matching of special token texts between two tokenizers. - Purpose is to produce replacement pairs so special token test is correctly represented for target tokenizer. - Args: - from_tokenizer (:obj:`PreTrainedTokenizerBase`, `required`): - From tokenizer. - to_tokenizer (:obj:`PreTrainedTokenizerBase`, `required`): - To tokenizer. - - Returns: - pairings (:obj:`Dict[str, str]`, `required`): - Prioritized dictionary of From_special_token_text -> To_special_token_text. - """ - pairings = {} - - # some tokenizers e.g. GPT2 have the same text signifying BOS and EOS, while in other e.g. XGLM they differ - # so prioritize EOS token first, since this seems to be the default context separator, e.g. XGLM, GerPT2, GPT2 - if ("eos_token" in from_tokenizer.special_tokens_map) and ( - "eos_token" in to_tokenizer.special_tokens_map - ): - pairings[getattr(from_tokenizer, "eos_token")] = getattr( - to_tokenizer, "eos_token" - ) - - for special_token in from_tokenizer.special_tokens_map: - if special_token in to_tokenizer.special_tokens_map: - if ( - getattr(from_tokenizer, special_token) not in pairings - ): # prevent priority overwrite - pairings[getattr(from_tokenizer, special_token)] = getattr( - to_tokenizer, special_token - ) - - return pairings - - -def translate_special_token_text( - text_batch: List[str], - from_tokenizer: PreTrainedTokenizerBase, - to_tokenizer: PreTrainedTokenizerBase, -) -> Tuple[ - List[str], List[List[List[int]]], List[List[List[int]]], List[List[List[Any]]] -]: - r""" - Translates special_token signifier text in from_tokenizer to to_tokenizer special_token text, for - a given text_batch. Resulting to_text_batch can then be to_tokenized where special_tokens should - map to its single corresponding token, despite signifier text difference compared to from_tokenizer. - Args: - text_batch (:obj:`List[str]`, `required`): - List of strings to translate special tokens for. - from_tokenizer (:obj:`PreTrainedTokenizerBase`, `required`): - From tokenizer. - to_tokenizer (:obj:`PreTrainedTokenizerBase`, `required`): - To tokenizer. - - Returns: - to_text_batch (:obj:`List[str]`, `required`): - List of strings where special text has been replaced. - from_offsets_batch (:obj:`List[List[List[int]]]`, `required`): - Batch of tokenizer offset mappings selecting replacement tuples in from_tokenizer text - [[(left_0, right_0), (left_1, right_1), ...], ...]. - to_offsets_batch (:obj:`List[List[List[int]]]`, `required`): - Batch of tokenizer offset mappings selecting replacement tuples in to_tokenizer text - [[(left_0, right_0), (left_1, right_1), ...], ...]. - pad_offsets_batch (:obj:`List[List[List[Any]]]`, `required`): - Batch of offset paddings associated with each replacement tuple - [[(left_pad_0, right_pad_0), (left_pad_1, right_pad_1), ...], ...]. - """ - to_text_batch = [] - from_offsets_batch = [] - to_offsets_batch = [] - pad_offsets_batch = [] - - # === Get special-token text replacement pairs === - pairings = get_special_token_pairings(from_tokenizer, to_tokenizer) - - for text in text_batch: - from_offsets = [] - padding_offsets = [] - for token_string in pairings: - offsets = find_offsets(text, token_string) # find special-token locations - from_offsets += [ - [left, right, pairings[token_string]] for left, right in offsets - ] - - pad_string = ( - token_string - if len(token_string) > len(pairings[token_string]) - else pairings[token_string] - ) - padding_offsets += [[left, right, pad_string] for left, right in offsets] - - from_offsets = sorted(from_offsets) # incrementally arrange locations - to_text, to_offsets = replace_at_offsets( - text, from_offsets - ) # replace special-token text - pad_text, padding_offsets = replace_at_offsets( - text, padding_offsets - ) # pad special-token text locations - - to_text_batch += [to_text] - from_offsets_batch += [[[left, right] for left, right, _ in from_offsets]] - to_offsets_batch += [to_offsets] - pad_offsets_batch += [padding_offsets] - - return to_text_batch, from_offsets_batch, to_offsets_batch, pad_offsets_batch - - -def set_vocab_len(tokenizer: PreTrainedTokenizerBase): - r""" - Sets the tokenizer.vocab_len if unset, to store the real vocabulary size according to the vocab or encoder. - Args: - tokenizer (:obj:`PreTrainedTokenizerBase`, `required`): - Tokenizer to set vocab_len for. - Returns: - - """ - if not hasattr(tokenizer, "vocab_len"): - if hasattr( - tokenizer, "vocab" - ): # use independent vocab_len when tokenizer.vocab_size != len(tokenizer.vocab) - tokenizer.vocab_len = len(tokenizer.vocab) - elif hasattr( - tokenizer, "encoder" - ): # tokenizers like facebook/opt-* has encoder=vocab - tokenizer.vocab_len = len(tokenizer.encoder) - else: # revert to vocab_size - tokenizer.vocab_len = tokenizer.vocab_size - - -def set_whitespace_preserving(tokenizer: PreTrainedTokenizerBase): - r""" - Sets the tokenizer.whitespace_preserving if unset, indicates if tokenizer preserves whitespace like GPT-style, - or not like BERT-style. - Args: - tokenizer (:obj:`PreTrainedTokenizerBase`, `required`): - Tokenizer to set vocab_len for. - Returns: - - """ - if not hasattr(tokenizer, "whitespace_preserving"): - space_token = tokenizer(" ", add_special_tokens=False)["input_ids"] - space_text = tokenizer.decode(space_token) - if space_text == " ": - tokenizer.whitespace_preserving = True - else: - tokenizer.whitespace_preserving = False - - -def set_std_token_phrases(tokenizer, std_tokenizer): - r""" - Sets std_token_phrases which are the tokenizer token strings tokenized with std_tokenizer, so - the std_tokenizer equivalent of the tokenizer token strings. - Used for converting model predictions/logits into std_tokenizer representations, for example in TextCausalLMNext. - Args: - tokenizer(:obj:`PreTrainedTokenizerBase`, `required`): - Tokenizer to set std_token_phrases for. - std_tokenizer(:obj:`PreTrainedTokenizerBase`, `required`): - Standard bittensor tokenizer to convert to. - - Returns: - - """ - # === Tokenizer phrases to memory === - if not hasattr(tokenizer, "phrases"): - if tokenizer.whitespace_preserving: - tokenizer.phrases = tokenizer.batch_decode( - range(tokenizer.vocab_len) - ) # server tokens to strings - else: - tokenizer.phrases = [ - " " + phrase - for phrase in tokenizer.batch_decode(range(tokenizer.vocab_len)) - ] # server tokens to strings - - if not hasattr(tokenizer, "std_token_phrases"): - # Retokenize phrases to new tokenizer - tokenizer.std_token_phrases = std_tokenizer(tokenizer.phrases)[ - "input_ids" - ] # [topk, max_len] convert phrases to tokens sequences - - -def prep_tokenizer(tokenizer, std_tokenizer=None): - tokenizer.padding_side = "left" # Generative default expects most recent token on right-hand side with padding on left. https://github.com/huggingface/transformers/pull/10552 - # tokenizer.add_prefix_space = False - # tokenizer.add_special_tokens({'bos_token': "[BOS]"}) # A special token representing the beginning of a sentence. - # tokenizer.add_special_tokens({'eos_token': "[EOS]"}) # A special token representing the end of a sentence. - # tokenizer.add_special_tokens({'unk_token': "[UNK]"}) # A special token representing an out-of-vocabulary token. - # tokenizer.add_special_tokens({'sep_token': "[SEP]"}) # A special token separating two different sentences in the same input (used by BERT for instance) - # tokenizer.add_special_tokens({'pad_token': "[PAD]"}) # A special token used to make arrays of tokens the same size for batching purpose. Will then be ignored by attention mechanisms or loss computation. - # tokenizer.add_special_tokens({'cls_token': "[CLS]"}) # A special token representing the class of the input (used by BERT for instance). - # tokenizer.add_special_tokens({'mask_token': "[MASK]"}) # A special token representing a masked token (used by masked-language modeling pretraining objectives, like BERT). - # additional_special_tokens = [ - # "NOTUSED", # Used by BARThez - # "NOTUSED", # Used by BARThez - # "", # Used by MarianMT - # "", # Used by MarianMT - # "", # Used by Transformer XL - # "" # Used by Pegasus - # "", # Used by XLM - # "", # Used by XLM - # "", # Used by XLM - # "", # Used by XLM - # "", # Used by XLM - # "", # Used by XLM - # "", # Used by XLM - # "", # Used by XLM - # "", # Used by XLM - # "", # Used by XLM - # ] - # tokenizer.additional_special_tokens = additional_special_tokens - - # Define PAD Token = EOS Token (GPT2 generate convention, when PAD Token is None) - # https://github.com/huggingface/transformers/blob/49c8c67fb815a277405f84dea4a66353e19fb347/tests/models/gpt2/test_modeling_gpt2.py#L532 - if tokenizer.pad_token_id is None and tokenizer.eos_token_id is not None: - tokenizer.pad_token = tokenizer.eos_token - set_vocab_len(tokenizer) - set_whitespace_preserving(tokenizer) - - if std_tokenizer is not None: - set_std_token_phrases(tokenizer, std_tokenizer) - - return tokenizer diff --git a/bittensor/utils/wallet_utils.py b/bittensor/utils/wallet_utils.py new file mode 100644 index 0000000000..8768d3a5ba --- /dev/null +++ b/bittensor/utils/wallet_utils.py @@ -0,0 +1,104 @@ +# The MIT License (MIT) +# Copyright © 2021 Yuma Rao +# Copyright © 2022 Opentensor Foundation +# Copyright © 2023 Opentensor Technologies Inc + +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the “Software”), to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of +# the Software. + +# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# 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. + +from substrateinterface.utils import ss58 +from typing import Union + +from .. import __ss58_format__ +from substrateinterface import Keypair as Keypair + + +def get_ss58_format(ss58_address: str) -> int: + """Returns the ss58 format of the given ss58 address.""" + return ss58.get_ss58_format(ss58_address) + + +def is_valid_ss58_address(address: str) -> bool: + """ + Checks if the given address is a valid ss58 address. + + Args: + address(str): The address to check. + + Returns: + True if the address is a valid ss58 address for Bittensor, False otherwise. + """ + try: + return ss58.is_valid_ss58_address( + address, valid_ss58_format=__ss58_format__ + ) or ss58.is_valid_ss58_address( + address, valid_ss58_format=42 + ) # Default substrate ss58 format (legacy) + except IndexError: + return False + + +def is_valid_ed25519_pubkey(public_key: Union[str, bytes]) -> bool: + """ + Checks if the given public_key is a valid ed25519 key. + + Args: + public_key(Union[str, bytes]): The public_key to check. + + Returns: + True if the public_key is a valid ed25519 key, False otherwise. + + """ + try: + if isinstance(public_key, str): + if len(public_key) != 64 and len(public_key) != 66: + raise ValueError("a public_key should be 64 or 66 characters") + elif isinstance(public_key, bytes): + if len(public_key) != 32: + raise ValueError("a public_key should be 32 bytes") + else: + raise ValueError("public_key must be a string or bytes") + + keypair = Keypair(public_key=public_key, ss58_format=__ss58_format__) + + ss58_addr = keypair.ss58_address + return ss58_addr is not None + + except (ValueError, IndexError): + return False + + +def is_valid_bittensor_address_or_public_key(address: Union[str, bytes]) -> bool: + """ + Checks if the given address is a valid destination address. + + Args: + address(Union[str, bytes]): The address to check. + + Returns: + True if the address is a valid destination address, False otherwise. + """ + if isinstance(address, str): + # Check if ed25519 + if address.startswith("0x"): + return is_valid_ed25519_pubkey(address) + else: + # Assume ss58 address + return is_valid_ss58_address(address) + elif isinstance(address, bytes): + # Check if ed25519 + return is_valid_ed25519_pubkey(address) + else: + # Invalid address type + return False diff --git a/bittensor/utils/weight_utils.py b/bittensor/utils/weight_utils.py index ca111322b4..d07caf22aa 100644 --- a/bittensor/utils/weight_utils.py +++ b/bittensor/utils/weight_utils.py @@ -100,6 +100,39 @@ def convert_weight_uids_and_vals_to_tensor( return row_weights +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) + Args: + n: int: + number of neurons on network. + uids (:obj:`List[int],`): + Tensor of uids as destinations for passed weights. + weights (:obj:`List[int],`): + Tensor of weights. + subnets (:obj:`List[int],`): + list of subnets on the network + Returns: + row_weights ( torch.FloatTensor ): + Converted row weights. + """ + + row_weights = torch.zeros([n], dtype=torch.float32) + for uid_j, wij in list(zip(uids, weights)): + if uid_j in subnets: + index_s = subnets.index(uid_j) + else: + raise Exception("Incorrect Subnet {uid_j} in {subnets}") + row_weights[index_s] = float( + wij + ) # assumes max-upscaled values (w_max = U16_MAX). + row_sum = row_weights.sum() + if row_sum > 0: + row_weights /= row_sum # normalize + return row_weights + + def convert_bond_uids_and_vals_to_tensor( n: int, uids: List[int], bonds: List[int] ) -> "torch.LongTensor": @@ -180,6 +213,7 @@ def process_weights_for_netuid( netuid: int, subtensor: "bittensor.subtensor", metagraph: "bittensor.metagraph" = None, + exclude_quantile: int = 6554, ) -> torch.FloatTensor: bittensor.logging.debug("process_weights_for_netuid()") bittensor.logging.debug("weights", weights) @@ -197,7 +231,7 @@ def process_weights_for_netuid( # Network configuration parameters from an subtensor. # These parameters determine the range of acceptable weights for each neuron. - quantile = subtensor.validator_exclude_quantile(netuid=netuid) + quantile = exclude_quantile min_allowed_weights = subtensor.min_allowed_weights(netuid=netuid) max_weight_limit = subtensor.max_weight_limit(netuid=netuid) bittensor.logging.debug("quantile", quantile) diff --git a/bittensor/wallet.py b/bittensor/wallet.py new file mode 100644 index 0000000000..19953f264a --- /dev/null +++ b/bittensor/wallet.py @@ -0,0 +1,816 @@ +""" Implementation of the wallet class, which manages balances with staking and transfer. Also manages hotkey and coldkey. +""" +# The MIT License (MIT) +# Copyright © 2021 Yuma Rao + +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the “Software”), to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of +# the Software. + +# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# 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 os +import copy +import argparse +import bittensor +from termcolor import colored +from substrateinterface import Keypair +from typing import Optional, Union, List, Tuple, Dict, overload +from bittensor.utils import is_valid_bittensor_address_or_public_key + + +def display_mnemonic_msg(keypair: Keypair, key_type: str): + """ + Display the mnemonic and a warning message to keep the mnemonic safe. + + Args: + keypair (Keypair): Keypair object. + key_type (str): Type of the key (coldkey or hotkey). + """ + mnemonic = keypair.mnemonic + mnemonic_green = colored(mnemonic, "green") + print( + colored( + "\nIMPORTANT: Store this mnemonic in a secure (preferable offline place), as anyone " + "who has possession of this mnemonic can use it to regenerate the key and access your tokens. \n", + "red", + ) + ) + print("The mnemonic to the new {} is:\n\n{}\n".format(key_type, mnemonic_green)) + print( + "You can use the mnemonic to recreate the key in case it gets lost. The command to use to regenerate the key using this mnemonic is:" + ) + print("btcli w regen_{} --mnemonic {}".format(key_type, mnemonic)) + print("") + + +class wallet: + """ + Bittensor wallet maintenance class. Each wallet contains a coldkey and a hotkey. + The coldkey is the user's primary key for holding stake in their wallet + and is the only way that users can access Tao. Coldkeys can hold tokens and should be encrypted on your device. + The coldkey must be used to stake and unstake funds from a running node. The hotkey, on the other hand, is only used + for subscribing and setting weights from running code. Hotkeys are linked to coldkeys through the metagraph. + """ + + @classmethod + def config(cls) -> "bittensor.config": + """ + Get config from the argument parser. + + Returns: + bittensor.config: Config object. + """ + parser = argparse.ArgumentParser() + cls.add_args(parser) + return bittensor.config(parser, args=[]) + + @classmethod + def help(cls): + """ + Print help to stdout. + """ + parser = argparse.ArgumentParser() + cls.add_args(parser) + print(cls.__new__.__doc__) + parser.print_help() + + @classmethod + def add_args(cls, parser: argparse.ArgumentParser, prefix: str = None): + """ + Accept specific arguments from parser. + + Args: + parser (argparse.ArgumentParser): Argument parser object. + prefix (str): Argument prefix. + """ + prefix_str = "" if prefix == None else prefix + "." + try: + default_name = os.getenv("BT_WALLET_NAME") or "default" + default_hotkey = os.getenv("BT_WALLET_NAME") or "default" + default_path = os.getenv("BT_WALLET_PATH") or "~/.bittensor/wallets/" + parser.add_argument( + "--" + prefix_str + "wallet.name", + required=False, + default=default_name, + help="The name of the wallet to unlock for running bittensor " + "(name mock is reserved for mocking this wallet)", + ) + parser.add_argument( + "--" + prefix_str + "wallet.hotkey", + required=False, + default=default_hotkey, + help="The name of the wallet's hotkey.", + ) + parser.add_argument( + "--" + prefix_str + "wallet.path", + required=False, + default=default_path, + help="The path to your bittensor wallets", + ) + except argparse.ArgumentError as e: + pass + + def __init__( + self, + name: str = None, + hotkey: str = None, + path: str = None, + config: "bittensor.config" = None, + ): + r""" + Initialize the bittensor wallet object containing a hot and coldkey. + + Args: + name (str, optional): The name of the wallet to unlock for running bittensor. Defaults to 'default'. + hotkey (str, optional): The name of hotkey used to running the miner. Defaults to 'default'. + path (str, optional): The path to your bittensor wallets. Defaults to '~/.bittensor/wallets/'. + config (bittensor.config, optional): bittensor.wallet.config(). Defaults to None. + """ + # Fill config from passed args using command line defaults. + if config is None: + config = wallet.config() + self.config = copy.deepcopy(config) + self.config.wallet.name = name or self.config.wallet.get( + "name", bittensor.defaults.wallet.name + ) + self.config.wallet.hotkey = hotkey or self.config.wallet.get( + "hotkey", bittensor.defaults.wallet.hotkey + ) + self.config.wallet.path = path or self.config.wallet.get( + "path", bittensor.defaults.wallet.path + ) + + self.name = self.config.wallet.name + self.path = self.config.wallet.path + self.hotkey_str = self.config.wallet.hotkey + + self._hotkey = None + self._coldkey = None + self._coldkeypub = None + + def __str__(self): + """ + Returns the string representation of the Wallet object. + + Returns: + str: The string representation. + """ + return "wallet({}, {}, {})".format(self.name, self.hotkey_str, self.path) + + def __repr__(self): + """ + Returns the string representation of the Wallet object. + + Returns: + str: The string representation. + """ + return self.__str__() + + def create_if_non_existent( + self, coldkey_use_password: bool = True, hotkey_use_password: bool = False + ) -> "wallet": + """ + Checks for existing coldkeypub and hotkeys and creates them if non-existent. + + Args: + coldkey_use_password (bool, optional): Whether to use a password for coldkey. Defaults to True. + hotkey_use_password (bool, optional): Whether to use a password for hotkey. Defaults to False. + + Returns: + wallet: The Wallet object. + """ + return self.create(coldkey_use_password, hotkey_use_password) + + def create( + self, coldkey_use_password: bool = True, hotkey_use_password: bool = False + ) -> "wallet": + """ + Checks for existing coldkeypub and hotkeys and creates them if non-existent. + + Args: + coldkey_use_password (bool, optional): Whether to use a password for coldkey. Defaults to True. + hotkey_use_password (bool, optional): Whether to use a password for hotkey. Defaults to False. + + Returns: + wallet: The Wallet object. + """ + # ---- Setup Wallet. ---- + if ( + not self.coldkey_file.exists_on_device() + and not self.coldkeypub_file.exists_on_device() + ): + self.create_new_coldkey(n_words=12, use_password=coldkey_use_password) + if not self.hotkey_file.exists_on_device(): + self.create_new_hotkey(n_words=12, use_password=hotkey_use_password) + return self + + def recreate( + self, coldkey_use_password: bool = True, hotkey_use_password: bool = False + ) -> "wallet": + """ + Checks for existing coldkeypub and hotkeys and creates them if non-existent. + + Args: + coldkey_use_password (bool, optional): Whether to use a password for coldkey. Defaults to True. + hotkey_use_password (bool, optional): Whether to use a password for hotkey. Defaults to False. + + Returns: + wallet: The Wallet object. + """ + # ---- Setup Wallet. ---- + self.create_new_coldkey(n_words=12, use_password=coldkey_use_password) + self.create_new_hotkey(n_words=12, use_password=hotkey_use_password) + return self + + @property + def hotkey_file(self) -> "bittensor.keyfile": + """ + Property that returns the hotkey file. + + Returns: + bittensor.keyfile: The hotkey file. + """ + wallet_path = os.path.expanduser(os.path.join(self.path, self.name)) + hotkey_path = os.path.join(wallet_path, "hotkeys", self.hotkey_str) + return bittensor.keyfile(path=hotkey_path) + + @property + def coldkey_file(self) -> "bittensor.keyfile": + """ + Property that returns the coldkey file. + + Returns: + bittensor.keyfile: The coldkey file. + """ + wallet_path = os.path.expanduser(os.path.join(self.path, self.name)) + coldkey_path = os.path.join(wallet_path, "coldkey") + return bittensor.keyfile(path=coldkey_path) + + @property + def coldkeypub_file(self) -> "bittensor.keyfile": + """ + Property that returns the coldkeypub file. + + Returns: + bittensor.keyfile: The coldkeypub file. + """ + wallet_path = os.path.expanduser(os.path.join(self.path, self.name)) + coldkeypub_path = os.path.join(wallet_path, "coldkeypub.txt") + return bittensor.keyfile(path=coldkeypub_path) + + def set_hotkey( + self, + keypair: "bittensor.Keypair", + encrypt: bool = False, + overwrite: bool = False, + ) -> "bittensor.keyfile": + """ + Sets the hotkey for the wallet. + + Args: + keypair (bittensor.Keypair): The hotkey keypair. + encrypt (bool, optional): Whether to encrypt the hotkey. Defaults to False. + overwrite (bool, optional): Whether to overwrite an existing hotkey. Defaults to False. + + Returns: + bittensor.keyfile: The hotkey file. + """ + self._hotkey = keypair + self.hotkey_file.set_keypair(keypair, encrypt=encrypt, overwrite=overwrite) + + def set_coldkeypub( + self, + keypair: "bittensor.Keypair", + encrypt: bool = False, + overwrite: bool = False, + ) -> "bittensor.keyfile": + """ + Sets the coldkeypub for the wallet. + + Args: + keypair (bittensor.Keypair): The coldkeypub keypair. + encrypt (bool, optional): Whether to encrypt the coldkeypub. Defaults to False. + overwrite (bool, optional): Whether to overwrite an existing coldkeypub. Defaults to False. + + Returns: + bittensor.keyfile: The coldkeypub file. + """ + self._coldkeypub = bittensor.Keypair(ss58_address=keypair.ss58_address) + self.coldkeypub_file.set_keypair( + self._coldkeypub, encrypt=encrypt, overwrite=overwrite + ) + + def set_coldkey( + self, + keypair: "bittensor.Keypair", + encrypt: bool = True, + overwrite: bool = False, + ) -> "bittensor.keyfile": + """ + Sets the coldkey for the wallet. + + Args: + keypair (bittensor.Keypair): The coldkey keypair. + encrypt (bool, optional): Whether to encrypt the coldkey. Defaults to True. + overwrite (bool, optional): Whether to overwrite an existing coldkey. Defaults to False. + + Returns: + bittensor.keyfile: The coldkey file. + """ + self._coldkey = keypair + self.coldkey_file.set_keypair( + self._coldkey, encrypt=encrypt, overwrite=overwrite + ) + + def get_coldkey(self, password: str = None) -> "bittensor.Keypair": + """ + Gets the coldkey from the wallet. + + Args: + password (str, optional): The password to decrypt the coldkey. Defaults to None. + + Returns: + bittensor.Keypair: The coldkey keypair. + """ + return self.coldkey_file.get_keypair(password=password) + + def get_hotkey(self, password: str = None) -> "bittensor.Keypair": + """ + Gets the hotkey from the wallet. + + Args: + password (str, optional): The password to decrypt the hotkey. Defaults to None. + + Returns: + bittensor.Keypair: The hotkey keypair. + """ + return self.hotkey_file.get_keypair(password=password) + + def get_coldkeypub(self, password: str = None) -> "bittensor.Keypair": + """ + Gets the coldkeypub from the wallet. + + Args: + password (str, optional): The password to decrypt the coldkeypub. Defaults to None. + + Returns: + bittensor.Keypair: The coldkeypub keypair. + """ + return self.coldkeypub_file.get_keypair(password=password) + + @property + def hotkey(self) -> "bittensor.Keypair": + r"""Loads the hotkey from wallet.path/wallet.name/hotkeys/wallet.hotkey or raises an error. + Returns: + hotkey (Keypair): + hotkey loaded from config arguments. + Raises: + KeyFileError: Raised if the file is corrupt of non-existent. + CryptoKeyError: Raised if the user enters an incorrec password for an encrypted keyfile. + """ + if self._hotkey == None: + self._hotkey = self.hotkey_file.keypair + return self._hotkey + + @property + def coldkey(self) -> "bittensor.Keypair": + r"""Loads the hotkey from wallet.path/wallet.name/coldkey or raises an error. + Returns: + coldkey (Keypair): + colkey loaded from config arguments. + Raises: + KeyFileError: Raised if the file is corrupt of non-existent. + CryptoKeyError: Raised if the user enters an incorrec password for an encrypted keyfile. + """ + if self._coldkey == None: + self._coldkey = self.coldkey_file.keypair + return self._coldkey + + @property + def coldkeypub(self) -> "bittensor.Keypair": + r"""Loads the coldkeypub from wallet.path/wallet.name/coldkeypub.txt or raises an error. + Returns: + coldkeypub (Keypair): + colkeypub loaded from config arguments. + Raises: + KeyFileError: Raised if the file is corrupt of non-existent. + CryptoKeyError: Raised if the user enters an incorrect password for an encrypted keyfile. + """ + if self._coldkeypub == None: + self._coldkeypub = self.coldkeypub_file.keypair + return self._coldkeypub + + def create_coldkey_from_uri( + self, + uri: str, + use_password: bool = True, + overwrite: bool = False, + suppress: bool = False, + ) -> "wallet": + """Creates coldkey from suri string, optionally encrypts it with the user's inputed password. + Args: + uri: (str, required): + URI string to use i.e. /Alice or /Bob + use_password (bool, optional): + Is the created key password protected. + overwrite (bool, optional): + Will this operation overwrite the coldkey under the same path //coldkey + Returns: + wallet (bittensor.wallet): + this object with newly created coldkey. + """ + keypair = Keypair.create_from_uri(uri) + if not suppress: + display_mnemonic_msg(keypair, "coldkey") + self.set_coldkey(keypair, encrypt=use_password, overwrite=overwrite) + self.set_coldkeypub(keypair, overwrite=overwrite) + return self + + def create_hotkey_from_uri( + self, + uri: str, + use_password: bool = False, + overwrite: bool = False, + suppress: bool = False, + ) -> "wallet": + """Creates hotkey from suri string, optionally encrypts it with the user's inputed password. + Args: + uri: (str, required): + URI string to use i.e. /Alice or /Bob + use_password (bool, optional): + Is the created key password protected. + overwrite (bool, optional): + Will this operation overwrite the hotkey under the same path //hotkeys/ + Returns: + wallet (bittensor.wallet): + this object with newly created hotkey. + """ + keypair = Keypair.create_from_uri(uri) + if not suppress: + display_mnemonic_msg(keypair, "hotkey") + self.set_hotkey(keypair, encrypt=use_password, overwrite=overwrite) + return self + + def new_coldkey( + self, + n_words: int = 12, + use_password: bool = True, + overwrite: bool = False, + suppress: bool = False, + ) -> "wallet": + """Creates a new coldkey, optionally encrypts it with the user's inputed password and saves to disk. + Args: + n_words: (int, optional): + Number of mnemonic words to use. + use_password (bool, optional): + Is the created key password protected. + overwrite (bool, optional): + Will this operation overwrite the coldkey under the same path //coldkey + Returns: + wallet (bittensor.wallet): + this object with newly created coldkey. + """ + self.create_new_coldkey(n_words, use_password, overwrite, suppress) + + def create_new_coldkey( + self, + n_words: int = 12, + use_password: bool = True, + overwrite: bool = False, + suppress: bool = False, + ) -> "wallet": + """Creates a new coldkey, optionally encrypts it with the user's inputed password and saves to disk. + Args: + n_words: (int, optional): + Number of mnemonic words to use. + use_password (bool, optional): + Is the created key password protected. + overwrite (bool, optional): + Will this operation overwrite the coldkey under the same path //coldkey + Returns: + wallet (bittensor.wallet): + this object with newly created coldkey. + """ + mnemonic = Keypair.generate_mnemonic(n_words) + keypair = Keypair.create_from_mnemonic(mnemonic) + if not suppress: + display_mnemonic_msg(keypair, "coldkey") + self.set_coldkey(keypair, encrypt=use_password, overwrite=overwrite) + self.set_coldkeypub(keypair, overwrite=overwrite) + return self + + def new_hotkey( + self, + n_words: int = 12, + use_password: bool = False, + overwrite: bool = False, + suppress: bool = False, + ) -> "wallet": + """Creates a new hotkey, optionally encrypts it with the user's inputed password and saves to disk. + Args: + n_words: (int, optional): + Number of mnemonic words to use. + use_password (bool, optional): + Is the created key password protected. + overwrite (bool, optional): + Will this operation overwrite the hotkey under the same path //hotkeys/ + Returns: + wallet (bittensor.wallet): + this object with newly created hotkey. + """ + self.create_new_hotkey(n_words, use_password, overwrite, suppress) + + def create_new_hotkey( + self, + n_words: int = 12, + use_password: bool = False, + overwrite: bool = False, + suppress: bool = False, + ) -> "wallet": + """Creates a new hotkey, optionally encrypts it with the user's inputed password and saves to disk. + Args: + n_words: (int, optional): + Number of mnemonic words to use. + use_password (bool, optional): + Is the created key password protected. + overwrite (bool, optional): + Will this operation overwrite the hotkey under the same path //hotkeys/ + Returns: + wallet (bittensor.wallet): + this object with newly created hotkey. + """ + mnemonic = Keypair.generate_mnemonic(n_words) + keypair = Keypair.create_from_mnemonic(mnemonic) + if not suppress: + display_mnemonic_msg(keypair, "hotkey") + self.set_hotkey(keypair, encrypt=use_password, overwrite=overwrite) + return self + + def regenerate_coldkeypub( + self, + ss58_address: Optional[str] = None, + public_key: Optional[Union[str, bytes]] = None, + overwrite: bool = False, + suppress: bool = False, + ) -> "wallet": + """Regenerates the coldkeypub from passed ss58_address or public_key and saves the file + Requires either ss58_address or public_key to be passed. + Args: + ss58_address: (str, optional): + Address as ss58 string. + public_key: (str | bytes, optional): + Public key as hex string or bytes. + overwrite (bool, optional) (default: False): + Will this operation overwrite the coldkeypub (if exists) under the same path //coldkeypub + Returns: + wallet (bittensor.wallet): + newly re-generated Wallet with coldkeypub. + + """ + if ss58_address is None and public_key is None: + raise ValueError("Either ss58_address or public_key must be passed") + + if not is_valid_bittensor_address_or_public_key( + ss58_address if ss58_address is not None else public_key + ): + raise ValueError( + f"Invalid {'ss58_address' if ss58_address is not None else 'public_key'}" + ) + + if ss58_address is not None: + ss58_format = bittensor.utils.get_ss58_format(ss58_address) + keypair = Keypair( + ss58_address=ss58_address, + public_key=public_key, + ss58_format=ss58_format, + ) + else: + keypair = Keypair( + ss58_address=ss58_address, + public_key=public_key, + ss58_format=bittensor.__ss58_format__, + ) + + # No need to encrypt the public key + self.set_coldkeypub(keypair, overwrite=overwrite) + + return self + + # Short name for regenerate_coldkeypub + regen_coldkeypub = regenerate_coldkeypub + + @overload + def regenerate_coldkey( + self, + mnemonic: Optional[Union[list, str]] = None, + use_password: bool = True, + overwrite: bool = False, + suppress: bool = False, + ) -> "wallet": + ... + + @overload + def regenerate_coldkey( + self, + seed: Optional[str] = None, + use_password: bool = True, + overwrite: bool = False, + suppress: bool = False, + ) -> "wallet": + ... + + @overload + def regenerate_coldkey( + self, + json: Optional[Tuple[Union[str, Dict], str]] = None, + use_password: bool = True, + overwrite: bool = False, + suppress: bool = False, + ) -> "wallet": + ... + + def regenerate_coldkey( + self, + use_password: bool = True, + overwrite: bool = False, + suppress: bool = False, + **kwargs, + ) -> "wallet": + """Regenerates the coldkey from passed mnemonic, seed, or json encrypts it with the user's password and saves the file + Args: + mnemonic: (Union[list, str], optional): + Key mnemonic as list of words or string space separated words. + seed: (str, optional): + Seed as hex string. + json: (Tuple[Union[str, Dict], str], optional): + Restore from encrypted JSON backup as (json_data: Union[str, Dict], passphrase: str) + use_password (bool, optional): + Is the created key password protected. + overwrite (bool, optional): + Will this operation overwrite the coldkey under the same path //coldkey + Returns: + wallet (bittensor.wallet): + this object with newly created coldkey. + + Note: uses priority order: mnemonic > seed > json + """ + if len(kwargs) == 0: + raise ValueError("Must pass either mnemonic, seed, or json") + + # Get from kwargs + mnemonic = kwargs.get("mnemonic", None) + seed = kwargs.get("seed", None) + json = kwargs.get("json", None) + + if mnemonic is None and seed is None and json is None: + raise ValueError("Must pass either mnemonic, seed, or json") + if mnemonic is not None: + if isinstance(mnemonic, str): + mnemonic = mnemonic.split() + if len(mnemonic) not in [12, 15, 18, 21, 24]: + raise ValueError( + "Mnemonic has invalid size. This should be 12,15,18,21 or 24 words" + ) + keypair = Keypair.create_from_mnemonic( + " ".join(mnemonic), ss58_format=bittensor.__ss58_format__ + ) + if not suppress: + display_mnemonic_msg(keypair, "coldkey") + elif seed is not None: + keypair = Keypair.create_from_seed( + seed, ss58_format=bittensor.__ss58_format__ + ) + else: + # json is not None + if ( + not isinstance(json, tuple) + or len(json) != 2 + or not isinstance(json[0], (str, dict)) + or not isinstance(json[1], str) + ): + raise ValueError( + "json must be a tuple of (json_data: str | Dict, passphrase: str)" + ) + + json_data, passphrase = json + keypair = Keypair.create_from_encrypted_json( + json_data, passphrase, ss58_format=bittensor.__ss58_format__ + ) + + self.set_coldkey(keypair, encrypt=use_password, overwrite=overwrite) + self.set_coldkeypub(keypair, overwrite=overwrite) + return self + + # Short name for regenerate_coldkey + regen_coldkey = regenerate_coldkey + + @overload + def regenerate_hotkey( + self, + mnemonic: Optional[Union[list, str]] = None, + use_password: bool = True, + overwrite: bool = False, + suppress: bool = False, + ) -> "wallet": + ... + + @overload + def regenerate_hotkey( + self, + seed: Optional[str] = None, + use_password: bool = True, + overwrite: bool = False, + suppress: bool = False, + ) -> "wallet": + ... + + @overload + def regenerate_hotkey( + self, + json: Optional[Tuple[Union[str, Dict], str]] = None, + use_password: bool = True, + overwrite: bool = False, + suppress: bool = False, + ) -> "wallet": + ... + + def regenerate_hotkey( + self, + use_password: bool = True, + overwrite: bool = False, + suppress: bool = False, + **kwargs, + ) -> "wallet": + """Regenerates the hotkey from passed mnemonic, encrypts it with the user's password and save the file + Args: + mnemonic: (Union[list, str], optional): + Key mnemonic as list of words or string space separated words. + seed: (str, optional): + Seed as hex string. + json: (Tuple[Union[str, Dict], str], optional): + Restore from encrypted JSON backup as (json_data: Union[str, Dict], passphrase: str) + use_password (bool, optional): + Is the created key password protected. + overwrite (bool, optional): + Will this operation overwrite the hotkey under the same path //hotkeys/ + Returns: + wallet (bittensor.wallet): + this object with newly created hotkey. + """ + if len(kwargs) == 0: + raise ValueError("Must pass either mnemonic, seed, or json") + + # Get from kwargs + mnemonic = kwargs.get("mnemonic", None) + seed = kwargs.get("seed", None) + json = kwargs.get("json", None) + + if mnemonic is None and seed is None and json is None: + raise ValueError("Must pass either mnemonic, seed, or json") + if mnemonic is not None: + if isinstance(mnemonic, str): + mnemonic = mnemonic.split() + if len(mnemonic) not in [12, 15, 18, 21, 24]: + raise ValueError( + "Mnemonic has invalid size. This should be 12,15,18,21 or 24 words" + ) + keypair = Keypair.create_from_mnemonic( + " ".join(mnemonic), ss58_format=bittensor.__ss58_format__ + ) + if not suppress: + display_mnemonic_msg(keypair, "hotkey") + elif seed is not None: + keypair = Keypair.create_from_seed( + seed, ss58_format=bittensor.__ss58_format__ + ) + else: + # json is not None + if ( + not isinstance(json, tuple) + or len(json) != 2 + or not isinstance(json[0], (str, dict)) + or not isinstance(json[1], str) + ): + raise ValueError( + "json must be a tuple of (json_data: str | Dict, passphrase: str)" + ) + + json_data, passphrase = json + keypair = Keypair.create_from_encrypted_json( + json_data, passphrase, ss58_format=bittensor.__ss58_format__ + ) + + self.set_hotkey(keypair, encrypt=use_password, overwrite=overwrite) + return self + + # Short name for regenerate_hotkey + regen_hotkey = regenerate_hotkey diff --git a/contrib/DEBUGGING.md b/contrib/DEBUGGING.md index dd54132b17..42cf5b7029 100644 --- a/contrib/DEBUGGING.md +++ b/contrib/DEBUGGING.md @@ -42,14 +42,14 @@ at the top of your script or source file to enable more verbose output logs. You can also write your own in the code simply: ```python # Bittensor's wallet maintenance class. -wallet = bt.wallet() +wallet = bittensor.wallet() bittensor.logging.debug( f"wallet keypair: {wallet.hotkey}" ) ... # Bittensor's chain state object. -metagraph = bt.metagraph(netuid=1) +metagraph = bittensor.metagraph(netuid=1) bittensor.logging.trace( f"metagraph created! netuid {metagraph.netuid}" ) ``` @@ -60,11 +60,11 @@ bittensor.logging.trace( f"metagraph created! netuid {metagraph.netuid}" ) Ensure you can query the Bittensor network using the Python API. If something is broken with your installation or the chain, this won't work out of the box. Here's an example of how to do this: ```python -import bittensor as bt -bt.trace() +import bittensor +bittensor.trace() # Attempt to query through the foundation endpoint. -print(bt.prompt("Heraclitus was a ")) +print(bittensor.prompt("Heraclitus was a ")) ``` ## Debugging Miners @@ -100,19 +100,19 @@ The Bittensor package contains data structures for interacting with the Bittenso Try to use the Bittensor package to create a wallet, connect to the axon running on slot 10, and send a prompt to this endpoint and see where things are breaking along this typical codepath: ```python -import bittensor as bt +import bittensor # Bittensor's wallet maintenance class. -wallet = bt.wallet() +wallet = bittensor.wallet() # Bittensor's chain interface. -subtensor = bt.subtensor() +subtensor = bittensor.subtensor() # Bittensor's chain state object. -metagraph = bt.metagraph(netuid=1) +metagraph = bittensor.metagraph(netuid=1) # Instantiate a Bittensor endpoint. -axon = bt.axon(wallet=wallet, metagraph=metagraph) +axon = bittensor.axon(wallet=wallet, metagraph=metagraph) # Start servicing messages on the wire. axon.start() @@ -121,7 +121,7 @@ axon.start() subtensor.serve_axon(netuid=1, axon=axon) # Connect to the axon running on slot 10, use the wallet to sign messages. -dendrite = bt.text_prompting(keypair=wallet.hotkey, axon=metagraph.axons[10]) +dendrite = bittensor.text_prompting(keypair=wallet.hotkey, axon=metagraph.axons[10]) # Send a prompt to this endpoint dendrite.forward(roles=['user'], messages=['Who is Rick James?']) diff --git a/docker-compose.yml b/docker-compose.yml index 31de3d1022..7e6933ed25 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,7 +7,4 @@ services: ports: - "8091:8091" volumes: - - ~/.bittensor:/root/.bittensor - - command: /bin/bash -c " - PYTHONPATH=/bittensor python3 /bittensor/miners/template_miner.py --subtensor.network nobunaga" + - ~/.bittensor:/root/.bittensor \ No newline at end of file diff --git a/examples/text_prompting.py b/examples/text_prompting.py deleted file mode 100644 index 4ef16ae085..0000000000 --- a/examples/text_prompting.py +++ /dev/null @@ -1,66 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2023 Yuma Rao - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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 -from typing import List, Dict, Union, Tuple - -bittensor.logging(bittensor.logging.config()) - - -class Synapse(bittensor.TextPromptingSynapse): - def priority(self, forward_call: "bittensor.TextPromptingForwardCall") -> float: - return 0.0 - - def blacklist( - self, forward_call: "bittensor.TextPromptingForwardCall" - ) -> Union[Tuple[bool, str], bool]: - return False - - def backward( - self, messages: List[Dict[str, str]], response: str, rewards: torch.FloatTensor - ) -> str: - pass - - def forward(self, messages: List[Dict[str, str]]) -> str: - return "hello im a chat bot." - - -# Create a mock wallet. -wallet = bittensor.wallet().create_if_non_existent() -axon = bittensor.axon(wallet=wallet, port=9090, external_ip="127.0.0.1") - -dendrite = bittensor.text_prompting(axon=axon, keypair=wallet.hotkey) -synapse = Synapse(axon=axon) -axon.start() - - -forward_call = dendrite.forward( - roles=["system", "assistant"], - messages=["you are chat bot", "what is the whether"], - timeout=1e6, -) -print(forward_call) -print( - "success", - forward_call.is_success, - "failed", - forward_call.did_fail, - "timedout", - forward_call.did_timeout, -) -print("completion", forward_call.completion) diff --git a/neurons/text/prompting/miners/AI21/README.md b/neurons/text/prompting/miners/AI21/README.md deleted file mode 100644 index cc6a58d43d..0000000000 --- a/neurons/text/prompting/miners/AI21/README.md +++ /dev/null @@ -1,114 +0,0 @@ -## AI21 Miner -AI21 Language Model Serving with BitTensor -This code is for running a language model powered by AI21 through the BitTensor framework. - -# Example Usage -``` -python3 -m pip install -r neurons/text/prompting/miners/AI21/requirements.txt -python3 neurons/text/prompting/miners/AI21/miner.py --ai21.api_key -``` - -# Full Usage -``` -usage: neuron.py [-h] --ai21.api_key AI21.API_KEY [--ai21.model_name AI21.MODEL_NAME] [--ai21.stop AI21.STOP] [--netuid NETUID] - [--neuron.name NEURON.NAME] [--neuron.blocks_per_epoch NEURON.BLOCKS_PER_EPOCH] [--neuron.no_set_weights] - [--neuron.max_batch_size NEURON.MAX_BATCH_SIZE] [--neuron.max_sequence_len NEURON.MAX_SEQUENCE_LEN] - [--neuron.blacklist.hotkeys [NEURON.BLACKLIST.HOTKEYS ...]] [--neuron.blacklist.allow_non_registered] - [--neuron.blacklist.default_stake NEURON.BLACKLIST.DEFAULT_STAKE] [--neuron.default_priority NEURON.DEFAULT_PRIORITY] - [--wallet.name WALLET.NAME] [--wallet.hotkey WALLET.HOTKEY] [--wallet.path WALLET.PATH] [--wallet._mock] - [--wallet.reregister WALLET.REREGISTER] [--axon.priority.max_workers AXON.PRIORITY.MAX_WORKERS] - [--axon.priority.maxsize AXON.PRIORITY.MAXSIZE] [--axon.port AXON.PORT] [--axon.ip AXON.IP] - [--axon.external_port AXON.EXTERNAL_PORT] [--axon.external_ip AXON.EXTERNAL_IP] [--axon.max_workers AXON.MAX_WORKERS] - [--axon.maximum_concurrent_rpcs AXON.MAXIMUM_CONCURRENT_RPCS] [--subtensor.network SUBTENSOR.NETWORK] - [--subtensor.chain_endpoint SUBTENSOR.CHAIN_ENDPOINT] [--subtensor._mock] - [--subtensor.register.num_processes SUBTENSOR.REGISTER.NUM_PROCESSES] - [--subtensor.register.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL] [--subtensor.register.no_output_in_place] - [--subtensor.register.verbose] [--subtensor.register.cuda.use_cuda] [--subtensor.register.cuda.no_cuda] - [--subtensor.register.cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...]] - [--subtensor.register.cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB] [--logging.debug] [--logging.trace] [--logging.record_log] - [--logging.logging_dir LOGGING.LOGGING_DIR] [--metagraph._mock] [--config CONFIG] [--strict] - -optional arguments: - -h, --help show this help message and exit - --ai21.api_key AI21.API_KEY - AI21 API key. - --ai21.model_name AI21.MODEL_NAME - Name of the model. - --ai21.stop AI21.STOP - Stop tokens. - --netuid NETUID Subnet netuid - --neuron.name NEURON.NAME - Trials for this miner go in miner.root / (wallet_cold - wallet_hot) / miner.name - --neuron.blocks_per_epoch NEURON.BLOCKS_PER_EPOCH - Blocks until the miner sets weights on chain - --neuron.no_set_weights - If True, the model does not set weights. - --neuron.max_batch_size NEURON.MAX_BATCH_SIZE - The maximum batch size for forward requests. - --neuron.max_sequence_len NEURON.MAX_SEQUENCE_LEN - The maximum sequence length for forward requests. - --neuron.blacklist.hotkeys [NEURON.BLACKLIST.HOTKEYS ...] - To blacklist certain hotkeys - --neuron.blacklist.allow_non_registered - If True, the miner will allow non-registered hotkeys to mine. - --neuron.blacklist.default_stake NEURON.BLACKLIST.DEFAULT_STAKE - Set default stake for miners. - --neuron.default_priority NEURON.DEFAULT_PRIORITY - Set default priority for miners. - --wallet.name WALLET.NAME - The name of the wallet to unlock for running bittensor (name mock is reserved for mocking this wallet) - --wallet.hotkey WALLET.HOTKEY - The name of wallet's hotkey. - --wallet.path WALLET.PATH - The path to your bittensor wallets - --wallet._mock To turn on wallet mocking for testing purposes. - --wallet.reregister WALLET.REREGISTER - Whether to reregister the wallet if it is not already registered. - --axon.priority.max_workers AXON.PRIORITY.MAX_WORKERS - maximum number of threads in thread pool - --axon.priority.maxsize AXON.PRIORITY.MAXSIZE - maximum size of tasks in priority queue - --axon.port AXON.PORT - The local port this axon endpoint is bound to. i.e. 8091 - --axon.ip AXON.IP The local ip this axon binds to. ie. [::] - --axon.external_port AXON.EXTERNAL_PORT - The public port this axon broadcasts to the network. i.e. 8091 - --axon.external_ip AXON.EXTERNAL_IP - The external ip this axon broadcasts to the network to. ie. [::] - --axon.max_workers AXON.MAX_WORKERS - The maximum number connection handler threads working simultaneously on this endpoint. The grpc server distributes - new worker threads to service requests up to this number. - --axon.maximum_concurrent_rpcs AXON.MAXIMUM_CONCURRENT_RPCS - Maximum number of allowed active connections - --subtensor.network SUBTENSOR.NETWORK - The subtensor network flag. The likely choices are: -- finney (main network) -- local (local running network) -- - mock (creates a mock connection (for testing)) If this option is set it overloads subtensor.chain_endpoint with an - entry point node from that network. - --subtensor.chain_endpoint SUBTENSOR.CHAIN_ENDPOINT - The subtensor endpoint flag. If set, overrides the --network flag. - --subtensor._mock To turn on subtensor mocking for testing purposes. - --subtensor.register.num_processes SUBTENSOR.REGISTER.NUM_PROCESSES, -n SUBTENSOR.REGISTER.NUM_PROCESSES - Number of processors to use for registration - --subtensor.register.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, --subtensor.register.cuda.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, --cuda.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, -u SUBTENSOR.REGISTER.UPDATE_INTERVAL - The number of nonces to process before checking for next block during registration - --subtensor.register.no_output_in_place, --no_output_in_place - Whether to not ouput the registration statistics in-place. Set flag to disable output in-place. - --subtensor.register.verbose - Whether to ouput the registration statistics verbosely. - --subtensor.register.cuda.use_cuda, --cuda, --cuda.use_cuda - Set flag to use CUDA to register. - --subtensor.register.cuda.no_cuda, --no_cuda, --cuda.no_cuda - Set flag to not use CUDA for registration - --subtensor.register.cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...], --cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...] - Set the CUDA device id(s). Goes by the order of speed. (i.e. 0 is the fastest). - --subtensor.register.cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB, --cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB - Set the number of Threads Per Block for CUDA. - --logging.debug Turn on bittensor debugging information - --logging.trace Turn on bittensor trace level information - --logging.record_log Turns on logging to file. - --logging.logging_dir LOGGING.LOGGING_DIR - Logging default root directory. - --metagraph._mock To turn on metagraph mocking for testing purposes. - --config CONFIG If set, defaults are overridden by passed file. - --strict If flagged, config will check that only exact arguemnts have been set. - ``` \ No newline at end of file diff --git a/neurons/text/prompting/miners/AI21/neuron.py b/neurons/text/prompting/miners/AI21/neuron.py deleted file mode 100644 index fc346dde28..0000000000 --- a/neurons/text/prompting/miners/AI21/neuron.py +++ /dev/null @@ -1,85 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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 argparse -import bittensor -from torch import FloatTensor - -from typing import List, Dict -from langchain.llms import AI21 - - -class AI21Miner(bittensor.BasePromptingMiner): - @classmethod - def check_config(cls, config: "bittensor.Config"): - assert ( - config.ai21.api_key != None - ), "the miner requires passing --ai21.api_key as an argument of the config." - - @classmethod - def add_args(cls, parser: argparse.ArgumentParser): - parser.add_argument( - "--ai21.api_key", type=str, help="AI21 API key.", required=True - ) - parser.add_argument( - "--ai21.model_name", - type=str, - help="Name of the model.", - default="j2-jumbo-instruct", - ) - parser.add_argument( - "--ai21.stop", help="Stop tokens.", default=["user: ", "bot: ", "system: "] - ) - - def __init__(self): - super(AI21Miner, self).__init__() - print(self.config) - - bittensor.logging.info("Loading AI21 Model...") - self.model = AI21( - model=self.config.ai21.model_name, - ai21_api_key=self.config.ai21.api_key, - stop=self.config.ai21.stop, - ) - bittensor.logging.info("Model loaded!") - - def backward( - self, messages: List[Dict[str, str]], response: str, rewards: FloatTensor - ) -> str: - pass - - @staticmethod - def _process_history(history: List[Dict[str, str]]) -> str: - processed_history = "" - for message in history: - if message["role"] == "system": - processed_history += "system: " + message["content"] + "\n" - if message["role"] == "assistant": - processed_history += "assistant: " + message["content"] + "\n" - if message["role"] == "user": - processed_history += "user: " + message["content"] + "\n" - return processed_history - - def forward(self, messages: List[Dict[str, str]]) -> str: - history = self._process_history(messages) - resp = self.model(history) - return resp - - -if __name__ == "__main__": - bittensor.utils.version_checking() - AI21Miner().run() diff --git a/neurons/text/prompting/miners/AI21/requirements.txt b/neurons/text/prompting/miners/AI21/requirements.txt deleted file mode 100644 index e7f08b2136..0000000000 --- a/neurons/text/prompting/miners/AI21/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -langchain \ No newline at end of file diff --git a/neurons/text/prompting/miners/AlephAlpha/README.md b/neurons/text/prompting/miners/AlephAlpha/README.md deleted file mode 100644 index da66c18d70..0000000000 --- a/neurons/text/prompting/miners/AlephAlpha/README.md +++ /dev/null @@ -1,124 +0,0 @@ -## AlephAlpha Miner -AlephAlpha Language Model Serving with BitTensor -This code is for running a language model powered by AlephAlpha through the BitTensor framework. - -# Example Usage -``` -python3 -m pip install -r neurons/text/prompting/miners/AlephAlpha/requirements.txt -python3 neurons/text/prompting/miners/AlephAlpha/miner.py --aleph.api_key -``` - -# Full Usage -``` -usage: neuron.py [-h] --aleph.api_key ALEPH.API_KEY [--aleph.model ALEPH.MODEL] [--aleph.maximum_tokens ALEPH.MAXIMUM_TOKENS] - [--aleph.temperature ALEPH.TEMPERATURE] [--aleph.stop_sequences ALEPH.STOP_SEQUENCES] [--aleph.top_k ALEPH.TOP_K] - [--aleph.top_p ALEPH.TOP_P] [--netuid NETUID] [--neuron.name NEURON.NAME] - [--neuron.blocks_per_epoch NEURON.BLOCKS_PER_EPOCH] [--neuron.no_set_weights] - [--neuron.max_batch_size NEURON.MAX_BATCH_SIZE] [--neuron.max_sequence_len NEURON.MAX_SEQUENCE_LEN] - [--neuron.blacklist.hotkeys [NEURON.BLACKLIST.HOTKEYS ...]] [--neuron.blacklist.allow_non_registered] - [--neuron.blacklist.default_stake NEURON.BLACKLIST.DEFAULT_STAKE] [--neuron.default_priority NEURON.DEFAULT_PRIORITY] - [--wallet.name WALLET.NAME] [--wallet.hotkey WALLET.HOTKEY] [--wallet.path WALLET.PATH] [--wallet._mock] - [--wallet.reregister WALLET.REREGISTER] [--axon.priority.max_workers AXON.PRIORITY.MAX_WORKERS] - [--axon.priority.maxsize AXON.PRIORITY.MAXSIZE] [--axon.port AXON.PORT] [--axon.ip AXON.IP] - [--axon.external_port AXON.EXTERNAL_PORT] [--axon.external_ip AXON.EXTERNAL_IP] [--axon.max_workers AXON.MAX_WORKERS] - [--axon.maximum_concurrent_rpcs AXON.MAXIMUM_CONCURRENT_RPCS] [--subtensor.network SUBTENSOR.NETWORK] - [--subtensor.chain_endpoint SUBTENSOR.CHAIN_ENDPOINT] [--subtensor._mock] - [--subtensor.register.num_processes SUBTENSOR.REGISTER.NUM_PROCESSES] - [--subtensor.register.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL] [--subtensor.register.no_output_in_place] - [--subtensor.register.verbose] [--subtensor.register.cuda.use_cuda] [--subtensor.register.cuda.no_cuda] - [--subtensor.register.cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...]] - [--subtensor.register.cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB] [--logging.debug] [--logging.trace] [--logging.record_log] - [--logging.logging_dir LOGGING.LOGGING_DIR] [--metagraph._mock] [--config CONFIG] [--strict] - -optional arguments: - -h, --help show this help message and exit - --aleph.api_key ALEPH.API_KEY - AlephAlpha API key. - --aleph.model ALEPH.MODEL - Model name to use. - --aleph.maximum_tokens ALEPH.MAXIMUM_TOKENS - The maximum number of tokens to be generated. - --aleph.temperature ALEPH.TEMPERATURE - A non-negative float that tunes the degree of randomness in generation. - --aleph.stop_sequences ALEPH.STOP_SEQUENCES - Stop tokens. - --aleph.top_k ALEPH.TOP_K - Number of most likely tokens to consider at each step. - --aleph.top_p ALEPH.TOP_P - Total probability mass of tokens to consider at each step. - --netuid NETUID Subnet netuid - --neuron.name NEURON.NAME - Trials for this miner go in miner.root / (wallet_cold - wallet_hot) / miner.name - --neuron.blocks_per_epoch NEURON.BLOCKS_PER_EPOCH - Blocks until the miner sets weights on chain - --neuron.no_set_weights - If True, the model does not set weights. - --neuron.max_batch_size NEURON.MAX_BATCH_SIZE - The maximum batch size for forward requests. - --neuron.max_sequence_len NEURON.MAX_SEQUENCE_LEN - The maximum sequence length for forward requests. - --neuron.blacklist.hotkeys [NEURON.BLACKLIST.HOTKEYS ...] - To blacklist certain hotkeys - --neuron.blacklist.allow_non_registered - If True, the miner will allow non-registered hotkeys to mine. - --neuron.blacklist.default_stake NEURON.BLACKLIST.DEFAULT_STAKE - Set default stake for miners. - --neuron.default_priority NEURON.DEFAULT_PRIORITY - Set default priority for miners. - --wallet.name WALLET.NAME - The name of the wallet to unlock for running bittensor (name mock is reserved for mocking this wallet) - --wallet.hotkey WALLET.HOTKEY - The name of wallet's hotkey. - --wallet.path WALLET.PATH - The path to your bittensor wallets - --wallet._mock To turn on wallet mocking for testing purposes. - --wallet.reregister WALLET.REREGISTER - Whether to reregister the wallet if it is not already registered. - --axon.priority.max_workers AXON.PRIORITY.MAX_WORKERS - maximum number of threads in thread pool - --axon.priority.maxsize AXON.PRIORITY.MAXSIZE - maximum size of tasks in priority queue - --axon.port AXON.PORT - The local port this axon endpoint is bound to. i.e. 8091 - --axon.ip AXON.IP The local ip this axon binds to. ie. [::] - --axon.external_port AXON.EXTERNAL_PORT - The public port this axon broadcasts to the network. i.e. 8091 - --axon.external_ip AXON.EXTERNAL_IP - The external ip this axon broadcasts to the network to. ie. [::] - --axon.max_workers AXON.MAX_WORKERS - The maximum number connection handler threads working simultaneously on this endpoint. The grpc server distributes - new worker threads to service requests up to this number. - --axon.maximum_concurrent_rpcs AXON.MAXIMUM_CONCURRENT_RPCS - Maximum number of allowed active connections - --subtensor.network SUBTENSOR.NETWORK - The subtensor network flag. The likely choices are: -- finney (main network) -- local (local running network) -- - mock (creates a mock connection (for testing)) If this option is set it overloads subtensor.chain_endpoint with an - entry point node from that network. - --subtensor.chain_endpoint SUBTENSOR.CHAIN_ENDPOINT - The subtensor endpoint flag. If set, overrides the --network flag. - --subtensor._mock To turn on subtensor mocking for testing purposes. - --subtensor.register.num_processes SUBTENSOR.REGISTER.NUM_PROCESSES, -n SUBTENSOR.REGISTER.NUM_PROCESSES - Number of processors to use for registration - --subtensor.register.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, --subtensor.register.cuda.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, --cuda.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, -u SUBTENSOR.REGISTER.UPDATE_INTERVAL - The number of nonces to process before checking for next block during registration - --subtensor.register.no_output_in_place, --no_output_in_place - Whether to not ouput the registration statistics in-place. Set flag to disable output in-place. - --subtensor.register.verbose - Whether to ouput the registration statistics verbosely. - --subtensor.register.cuda.use_cuda, --cuda, --cuda.use_cuda - Set flag to use CUDA to register. - --subtensor.register.cuda.no_cuda, --no_cuda, --cuda.no_cuda - Set flag to not use CUDA for registration - --subtensor.register.cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...], --cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...] - Set the CUDA device id(s). Goes by the order of speed. (i.e. 0 is the fastest). - --subtensor.register.cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB, --cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB - Set the number of Threads Per Block for CUDA. - --logging.debug Turn on bittensor debugging information - --logging.trace Turn on bittensor trace level information - --logging.record_log Turns on logging to file. - --logging.logging_dir LOGGING.LOGGING_DIR - Logging default root directory. - --metagraph._mock To turn on metagraph mocking for testing purposes. - --config CONFIG If set, defaults are overridden by passed file. - --strict If flagged, config will check that only exact arguemnts have been set. -``` \ No newline at end of file diff --git a/neurons/text/prompting/miners/AlephAlpha/neuron.py b/neurons/text/prompting/miners/AlephAlpha/neuron.py deleted file mode 100644 index ac0f5f11a8..0000000000 --- a/neurons/text/prompting/miners/AlephAlpha/neuron.py +++ /dev/null @@ -1,121 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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 argparse -import bittensor -from rich import print -from typing import List, Dict -from torch import FloatTensor - -from langchain.llms import AlephAlpha - - -class AlephAlphaMiner(bittensor.BasePromptingMiner): - @classmethod - def check_config(cls, config: "bittensor.Config"): - assert ( - config.aleph.api_key != None - ), "the miner requires passing --aleph.api_key as an argument of the config." - - @classmethod - def add_args(cls, parser: argparse.ArgumentParser): - parser.add_argument( - "--aleph.api_key", type=str, help="AlephAlpha API key.", required=True - ) - parser.add_argument( - "--aleph.model", - type=str, - help="Model name to use.", - default="luminous-base", - ) - parser.add_argument( - "--aleph.maximum_tokens", - type=int, - help="The maximum number of tokens to be generated.", - default=64, - ) - parser.add_argument( - "--aleph.temperature", - type=float, - help="A non-negative float that tunes the degree of randomness in generation.", - default=0.0, - ) - parser.add_argument( - "--aleph.stop_sequences", - type=List[str], - help="Stop tokens.", - default=["user: ", "bot: ", "system: "], - ) - parser.add_argument( - "--aleph.top_k", - type=int, - help="Number of most likely tokens to consider at each step.", - default=0, - ) - parser.add_argument( - "--aleph.top_p", - type=float, - help="Total probability mass of tokens to consider at each step.", - default=0.0, - ) - - def __init__(self): - super(AlephAlphaMiner, self).__init__() - print(self.config) - - self.model = AlephAlpha( - aleph_alpha_api_key=self.config.aleph.api_key, - model=self.config.aleph.model, - maximum_tokens=self.config.aleph.maximum_tokens, - temperature=self.config.aleph.temperature, - top_k=self.config.aleph.top_k, - top_p=self.config.aleph.top_p, - stop_sequences=self.config.aleph.stop_sequences, - ) - - def backward( - self, messages: List[Dict[str, str]], response: str, rewards: FloatTensor - ) -> str: - pass - - @staticmethod - def _process_history(history: List[Dict[str, str]]) -> str: - processed_history = "" - for message in history: - if message["role"] == "system": - processed_history += "system: " + message["content"] + "\n" - if message["role"] == "assistant": - processed_history += "assistant: " + message["content"] + "\n" - if message["role"] == "user": - processed_history += "user: " + message["content"] + "\n" - return processed_history - - def blacklist(self, forward_call: "bittensor.BittensorCall"): - return False - - def forward(self, messages: List[Dict[str, str]]) -> str: - bittensor.logging.info("messages", str(messages)) - history = self._process_history(messages) - bittensor.logging.info("history", str(history)) - resp = self.model(history) - bittensor.logging.info("response", str(resp)) - return resp - - -if __name__ == "__main__": - bittensor.utils.version_checking() - AlephAlphaMiner().run() diff --git a/neurons/text/prompting/miners/AlephAlpha/requiremenets.txt b/neurons/text/prompting/miners/AlephAlpha/requiremenets.txt deleted file mode 100644 index 110dc27b24..0000000000 --- a/neurons/text/prompting/miners/AlephAlpha/requiremenets.txt +++ /dev/null @@ -1,2 +0,0 @@ -aleph_alpha_client -langchain \ No newline at end of file diff --git a/neurons/text/prompting/miners/BTLM/README.md b/neurons/text/prompting/miners/BTLM/README.md deleted file mode 100644 index 71828ca82c..0000000000 --- a/neurons/text/prompting/miners/BTLM/README.md +++ /dev/null @@ -1,117 +0,0 @@ -## Bittensor LM (BTLM) Miner -Bittensor LM 1.3B Language Model -This code is for running the very small Bittenso Language Model created by Cerebras. - -# Example Usage -``` -python3 neurons/text/prompting/miners/cerebras/neuron.py -``` - -# Full Usage -``` -usage: neuron.py [-h] [--cerebras.device CEREBRAS.DEVICE] [--cerebras.max_length CEREBRAS.MAX_LENGTH] [--cerebras.do_sample] - [--cerebras.no_repeat_ngram_size CEREBRAS.NO_REPEAT_NGRAM_SIZE] - [--netuid NETUID] [--neuron.name NEURON.NAME] [--neuron.blocks_per_epoch NEURON.BLOCKS_PER_EPOCH] [--neuron.no_set_weights] - [--neuron.max_batch_size NEURON.MAX_BATCH_SIZE] [--neuron.max_sequence_len NEURON.MAX_SEQUENCE_LEN] - [--neuron.blacklist.hotkeys [NEURON.BLACKLIST.HOTKEYS ...]] [--neuron.blacklist.allow_non_registered] - [--neuron.blacklist.default_stake NEURON.BLACKLIST.DEFAULT_STAKE] [--neuron.default_priority NEURON.DEFAULT_PRIORITY] - [--wallet.name WALLET.NAME] [--wallet.hotkey WALLET.HOTKEY] [--wallet.path WALLET.PATH] [--wallet._mock] - [--wallet.reregister WALLET.REREGISTER] [--axon.priority.max_workers AXON.PRIORITY.MAX_WORKERS] - [--axon.priority.maxsize AXON.PRIORITY.MAXSIZE] [--axon.port AXON.PORT] [--axon.ip AXON.IP] - [--axon.external_port AXON.EXTERNAL_PORT] [--axon.external_ip AXON.EXTERNAL_IP] [--axon.max_workers AXON.MAX_WORKERS] - [--axon.maximum_concurrent_rpcs AXON.MAXIMUM_CONCURRENT_RPCS] [--subtensor.network SUBTENSOR.NETWORK] - [--subtensor.chain_endpoint SUBTENSOR.CHAIN_ENDPOINT] [--subtensor._mock] - [--subtensor.register.num_processes SUBTENSOR.REGISTER.NUM_PROCESSES] - [--subtensor.register.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL] [--subtensor.register.no_output_in_place] - [--subtensor.register.verbose] [--subtensor.register.cuda.use_cuda] [--subtensor.register.cuda.no_cuda] - [--subtensor.register.cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...]] - [--subtensor.register.cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB] [--logging.debug] [--logging.trace] [--logging.record_log] - [--logging.logging_dir LOGGING.LOGGING_DIR] [--metagraph._mock] [--config CONFIG] [--strict] - -optional arguments: - -h, --help show this help message and exit - --cerebras.device CEREBRAS.DEVICE - Device to load model - --cerebras.max_length CEREBRAS.MAX_LENGTH - The maximum length (in tokens) of the generated text. - --cerebras.do_sample Whether to use sampling or not (if not, uses greedy decoding). - --cerebras.no_repeat_ngram_size CEREBRAS.NO_REPEAT_NGRAM_SIZE - The size of the n-grams to avoid repeating in the generated text. - --cerebras.model_size {1.3B,2.7B,6.7B,13B} - Model size to use. - --netuid NETUID Subnet netuid - --neuron.name NEURON.NAME - Trials for this miner go in miner.root / (wallet_cold - wallet_hot) / miner.name - --neuron.blocks_per_epoch NEURON.BLOCKS_PER_EPOCH - Blocks until the miner sets weights on chain - --neuron.no_set_weights - If True, the model does not set weights. - --neuron.max_batch_size NEURON.MAX_BATCH_SIZE - The maximum batch size for forward requests. - --neuron.max_sequence_len NEURON.MAX_SEQUENCE_LEN - The maximum sequence length for forward requests. - --neuron.blacklist.hotkeys [NEURON.BLACKLIST.HOTKEYS ...] - To blacklist certain hotkeys - --neuron.blacklist.allow_non_registered - If True, the miner will allow non-registered hotkeys to mine. - --neuron.blacklist.default_stake NEURON.BLACKLIST.DEFAULT_STAKE - Set default stake for miners. - --neuron.default_priority NEURON.DEFAULT_PRIORITY - Set default priority for miners. - --wallet.name WALLET.NAME - The name of the wallet to unlock for running bittensor (name mock is reserved for mocking this wallet) - --wallet.hotkey WALLET.HOTKEY - The name of wallet's hotkey. - --wallet.path WALLET.PATH - The path to your bittensor wallets - --wallet._mock To turn on wallet mocking for testing purposes. - --wallet.reregister WALLET.REREGISTER - Whether to reregister the wallet if it is not already registered. - --axon.priority.max_workers AXON.PRIORITY.MAX_WORKERS - maximum number of threads in thread pool - --axon.priority.maxsize AXON.PRIORITY.MAXSIZE - maximum size of tasks in priority queue - --axon.port AXON.PORT - The local port this axon endpoint is bound to. i.e. 8091 - --axon.ip AXON.IP The local ip this axon binds to. ie. [::] - --axon.external_port AXON.EXTERNAL_PORT - The public port this axon broadcasts to the network. i.e. 8091 - --axon.external_ip AXON.EXTERNAL_IP - The external ip this axon broadcasts to the network to. ie. [::] - --axon.max_workers AXON.MAX_WORKERS - The maximum number connection handler threads working simultaneously on this endpoint. The grpc server distributes - new worker threads to service requests up to this number. - --axon.maximum_concurrent_rpcs AXON.MAXIMUM_CONCURRENT_RPCS - Maximum number of allowed active connections - --subtensor.network SUBTENSOR.NETWORK - The subtensor network flag. The likely choices are: -- finney (main network) -- local (local running network) -- - mock (creates a mock connection (for testing)) If this option is set it overloads subtensor.chain_endpoint with an - entry point node from that network. - --subtensor.chain_endpoint SUBTENSOR.CHAIN_ENDPOINT - The subtensor endpoint flag. If set, overrides the --network flag. - --subtensor._mock To turn on subtensor mocking for testing purposes. - --subtensor.register.num_processes SUBTENSOR.REGISTER.NUM_PROCESSES, -n SUBTENSOR.REGISTER.NUM_PROCESSES - Number of processors to use for registration - --subtensor.register.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, --subtensor.register.cuda.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, --cuda.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, -u SUBTENSOR.REGISTER.UPDATE_INTERVAL - The number of nonces to process before checking for next block during registration - --subtensor.register.no_output_in_place, --no_output_in_place - Whether to not ouput the registration statistics in-place. Set flag to disable output in-place. - --subtensor.register.verbose - Whether to ouput the registration statistics verbosely. - --subtensor.register.cuda.use_cuda, --cuda, --cuda.use_cuda - Set flag to use CUDA to register. - --subtensor.register.cuda.no_cuda, --no_cuda, --cuda.no_cuda - Set flag to not use CUDA for registration - --subtensor.register.cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...], --cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...] - Set the CUDA device id(s). Goes by the order of speed. (i.e. 0 is the fastest). - --subtensor.register.cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB, --cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB - Set the number of Threads Per Block for CUDA. - --logging.debug Turn on bittensor debugging information - --logging.trace Turn on bittensor trace level information - --logging.record_log Turns on logging to file. - --logging.logging_dir LOGGING.LOGGING_DIR - Logging default root directory. - --metagraph._mock To turn on metagraph mocking for testing purposes. - --config CONFIG If set, defaults are overridden by passed file. - --strict If flagged, config will check that only exact arguemnts have been set. - ``` \ No newline at end of file diff --git a/neurons/text/prompting/miners/BTLM/neuron.py b/neurons/text/prompting/miners/BTLM/neuron.py deleted file mode 100644 index 253e673f71..0000000000 --- a/neurons/text/prompting/miners/BTLM/neuron.py +++ /dev/null @@ -1,108 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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. - -# General. -import argparse -import bittensor -from typing import List, Dict -import torch -from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline - - -class CerebrasBTLMMiner(bittensor.BasePromptingMiner): - @classmethod - def check_config(cls, config: "bittensor.Config"): - pass - - @classmethod - def add_args(cls, parser: argparse.ArgumentParser): - parser.add_argument( - "--cerebras.device", type=str, help="Device to load model", default="cuda" - ) - parser.add_argument( - "--cerebras.max_length", - type=int, - default=50, - help="The maximum length (in tokens) of the generated text.", - ) - parser.add_argument( - "--cerebras.do_sample", - action="store_true", - default=False, - help="Whether to use sampling or not (if not, uses greedy decoding).", - ) - parser.add_argument( - "--cerebras.no_repeat_ngram_size", - type=int, - default=2, - help="The size of the n-grams to avoid repeating in the generated text.", - ) - - def __init__(self): - super(CerebrasBTLMMiner, self).__init__() - print(self.config) - - bittensor.logging.info( - "Loading BTLM {} model...".format(self.config.cerebras.model_size) - ) - model = AutoModelForCausalLM.from_pretrained( - "cerebras/btlm-3b-8k-base", trust_remote_code=True - ) - tokenizer = AutoTokenizer.from_pretrained( - "cerebras/btlm-3b-8k-base", - trust_remote_code=True, - ) - - self.pipe = pipeline( - "text-generation", - model=model, - tokenizer=tokenizer, - device=0, - do_sample=False, - max_new_tokens=self.config.cerebras.max_length, - no_repeat_ngram_size=self.config.cerebras.no_repeat_ngram_size, - ) - - def backward( - self, messages: List[Dict[str, str]], response: str, rewards: torch.FloatTensor - ) -> str: - pass - - @staticmethod - def _process_history(history: List[Dict[str, str]]) -> str: - processed_history = "" - for message in history: - if message["role"] == "system": - processed_history += "system: " + message["content"] + "\n" - if message["role"] == "assistant": - processed_history += "assistant: " + message["content"] + "\n" - if message["role"] == "user": - processed_history += "user: " + message["content"] + "\n" - return processed_history - - def forward(self, messages: List[Dict[str, str]]) -> str: - history = self._process_history(messages) - return ( - self.pipe(history)[0]["generated_text"] - .split(":")[-1] - .replace(str(history), "") - ) - - -if __name__ == "__main__": - bittensor.utils.version_checking() - CerebrasBTLMMiner().run() diff --git a/neurons/text/prompting/miners/cerebras/README.md b/neurons/text/prompting/miners/cerebras/README.md deleted file mode 100644 index 5bb5f52505..0000000000 --- a/neurons/text/prompting/miners/cerebras/README.md +++ /dev/null @@ -1,117 +0,0 @@ -## Cerebras Miner -Cerebras 13B Language Model Serving with BitTensor -This code is for running a language model powered by Cerebrus through the BitTensor framework. - -# Example Usage -``` -python3 neurons/text/prompting/miners/cerebras/neuron.py -``` - -# Full Usage -``` -usage: neuron.py [-h] [--cerebras.device CEREBRAS.DEVICE] [--cerebras.max_length CEREBRAS.MAX_LENGTH] [--cerebras.do_sample] - [--cerebras.no_repeat_ngram_size CEREBRAS.NO_REPEAT_NGRAM_SIZE] [--cerebras.model_size {1.3B,2.7B,6.7B,13B}] - [--netuid NETUID] [--neuron.name NEURON.NAME] [--neuron.blocks_per_epoch NEURON.BLOCKS_PER_EPOCH] [--neuron.no_set_weights] - [--neuron.max_batch_size NEURON.MAX_BATCH_SIZE] [--neuron.max_sequence_len NEURON.MAX_SEQUENCE_LEN] - [--neuron.blacklist.hotkeys [NEURON.BLACKLIST.HOTKEYS ...]] [--neuron.blacklist.allow_non_registered] - [--neuron.blacklist.default_stake NEURON.BLACKLIST.DEFAULT_STAKE] [--neuron.default_priority NEURON.DEFAULT_PRIORITY] - [--wallet.name WALLET.NAME] [--wallet.hotkey WALLET.HOTKEY] [--wallet.path WALLET.PATH] [--wallet._mock] - [--wallet.reregister WALLET.REREGISTER] [--axon.priority.max_workers AXON.PRIORITY.MAX_WORKERS] - [--axon.priority.maxsize AXON.PRIORITY.MAXSIZE] [--axon.port AXON.PORT] [--axon.ip AXON.IP] - [--axon.external_port AXON.EXTERNAL_PORT] [--axon.external_ip AXON.EXTERNAL_IP] [--axon.max_workers AXON.MAX_WORKERS] - [--axon.maximum_concurrent_rpcs AXON.MAXIMUM_CONCURRENT_RPCS] [--subtensor.network SUBTENSOR.NETWORK] - [--subtensor.chain_endpoint SUBTENSOR.CHAIN_ENDPOINT] [--subtensor._mock] - [--subtensor.register.num_processes SUBTENSOR.REGISTER.NUM_PROCESSES] - [--subtensor.register.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL] [--subtensor.register.no_output_in_place] - [--subtensor.register.verbose] [--subtensor.register.cuda.use_cuda] [--subtensor.register.cuda.no_cuda] - [--subtensor.register.cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...]] - [--subtensor.register.cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB] [--logging.debug] [--logging.trace] [--logging.record_log] - [--logging.logging_dir LOGGING.LOGGING_DIR] [--metagraph._mock] [--config CONFIG] [--strict] - -optional arguments: - -h, --help show this help message and exit - --cerebras.device CEREBRAS.DEVICE - Device to load model - --cerebras.max_length CEREBRAS.MAX_LENGTH - The maximum length (in tokens) of the generated text. - --cerebras.do_sample Whether to use sampling or not (if not, uses greedy decoding). - --cerebras.no_repeat_ngram_size CEREBRAS.NO_REPEAT_NGRAM_SIZE - The size of the n-grams to avoid repeating in the generated text. - --cerebras.model_size {1.3B,2.7B,6.7B,13B} - Model size to use. - --netuid NETUID Subnet netuid - --neuron.name NEURON.NAME - Trials for this miner go in miner.root / (wallet_cold - wallet_hot) / miner.name - --neuron.blocks_per_epoch NEURON.BLOCKS_PER_EPOCH - Blocks until the miner sets weights on chain - --neuron.no_set_weights - If True, the model does not set weights. - --neuron.max_batch_size NEURON.MAX_BATCH_SIZE - The maximum batch size for forward requests. - --neuron.max_sequence_len NEURON.MAX_SEQUENCE_LEN - The maximum sequence length for forward requests. - --neuron.blacklist.hotkeys [NEURON.BLACKLIST.HOTKEYS ...] - To blacklist certain hotkeys - --neuron.blacklist.allow_non_registered - If True, the miner will allow non-registered hotkeys to mine. - --neuron.blacklist.default_stake NEURON.BLACKLIST.DEFAULT_STAKE - Set default stake for miners. - --neuron.default_priority NEURON.DEFAULT_PRIORITY - Set default priority for miners. - --wallet.name WALLET.NAME - The name of the wallet to unlock for running bittensor (name mock is reserved for mocking this wallet) - --wallet.hotkey WALLET.HOTKEY - The name of wallet's hotkey. - --wallet.path WALLET.PATH - The path to your bittensor wallets - --wallet._mock To turn on wallet mocking for testing purposes. - --wallet.reregister WALLET.REREGISTER - Whether to reregister the wallet if it is not already registered. - --axon.priority.max_workers AXON.PRIORITY.MAX_WORKERS - maximum number of threads in thread pool - --axon.priority.maxsize AXON.PRIORITY.MAXSIZE - maximum size of tasks in priority queue - --axon.port AXON.PORT - The local port this axon endpoint is bound to. i.e. 8091 - --axon.ip AXON.IP The local ip this axon binds to. ie. [::] - --axon.external_port AXON.EXTERNAL_PORT - The public port this axon broadcasts to the network. i.e. 8091 - --axon.external_ip AXON.EXTERNAL_IP - The external ip this axon broadcasts to the network to. ie. [::] - --axon.max_workers AXON.MAX_WORKERS - The maximum number connection handler threads working simultaneously on this endpoint. The grpc server distributes - new worker threads to service requests up to this number. - --axon.maximum_concurrent_rpcs AXON.MAXIMUM_CONCURRENT_RPCS - Maximum number of allowed active connections - --subtensor.network SUBTENSOR.NETWORK - The subtensor network flag. The likely choices are: -- finney (main network) -- local (local running network) -- - mock (creates a mock connection (for testing)) If this option is set it overloads subtensor.chain_endpoint with an - entry point node from that network. - --subtensor.chain_endpoint SUBTENSOR.CHAIN_ENDPOINT - The subtensor endpoint flag. If set, overrides the --network flag. - --subtensor._mock To turn on subtensor mocking for testing purposes. - --subtensor.register.num_processes SUBTENSOR.REGISTER.NUM_PROCESSES, -n SUBTENSOR.REGISTER.NUM_PROCESSES - Number of processors to use for registration - --subtensor.register.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, --subtensor.register.cuda.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, --cuda.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, -u SUBTENSOR.REGISTER.UPDATE_INTERVAL - The number of nonces to process before checking for next block during registration - --subtensor.register.no_output_in_place, --no_output_in_place - Whether to not ouput the registration statistics in-place. Set flag to disable output in-place. - --subtensor.register.verbose - Whether to ouput the registration statistics verbosely. - --subtensor.register.cuda.use_cuda, --cuda, --cuda.use_cuda - Set flag to use CUDA to register. - --subtensor.register.cuda.no_cuda, --no_cuda, --cuda.no_cuda - Set flag to not use CUDA for registration - --subtensor.register.cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...], --cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...] - Set the CUDA device id(s). Goes by the order of speed. (i.e. 0 is the fastest). - --subtensor.register.cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB, --cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB - Set the number of Threads Per Block for CUDA. - --logging.debug Turn on bittensor debugging information - --logging.trace Turn on bittensor trace level information - --logging.record_log Turns on logging to file. - --logging.logging_dir LOGGING.LOGGING_DIR - Logging default root directory. - --metagraph._mock To turn on metagraph mocking for testing purposes. - --config CONFIG If set, defaults are overridden by passed file. - --strict If flagged, config will check that only exact arguemnts have been set. - ``` \ No newline at end of file diff --git a/neurons/text/prompting/miners/cerebras/neuron.py b/neurons/text/prompting/miners/cerebras/neuron.py deleted file mode 100644 index 13e758d327..0000000000 --- a/neurons/text/prompting/miners/cerebras/neuron.py +++ /dev/null @@ -1,114 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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. - -# General. -import argparse -import bittensor -from typing import List, Dict -from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline -from torch import FloatTensor - - -class CerebrasMiner(bittensor.BasePromptingMiner): - @classmethod - def check_config(cls, config: "bittensor.Config"): - pass - - @classmethod - def add_args(cls, parser: argparse.ArgumentParser): - parser.add_argument( - "--cerebras.device", type=str, help="Device to load model", default="cuda" - ) - parser.add_argument( - "--cerebras.max_length", - type=int, - default=50, - help="The maximum length (in tokens) of the generated text.", - ) - parser.add_argument( - "--cerebras.do_sample", - action="store_true", - default=False, - help="Whether to use sampling or not (if not, uses greedy decoding).", - ) - parser.add_argument( - "--cerebras.no_repeat_ngram_size", - type=int, - default=2, - help="The size of the n-grams to avoid repeating in the generated text.", - ) - parser.add_argument( - "--cerebras.model_size", - type=str, - choices=["1.3B", "2.7B", "6.7B", "13B"], - default="1.3B", - help="Model size to use.", - ) - - def __init__(self): - super(CerebrasMiner, self).__init__() - print(self.config) - - bittensor.logging.info( - "Loading Cerebras GPT {} model...".format(self.config.cerebras.model_size) - ) - model = AutoModelForCausalLM.from_pretrained( - "cerebras/Cerebras-GPT-{}".format(self.config.cerebras.model_size) - ) - tokenizer = AutoTokenizer.from_pretrained( - "cerebras/Cerebras-GPT-{}".format(self.config.cerebras.model_size) - ) - - self.pipe = pipeline( - "text-generation", - model=model, - tokenizer=tokenizer, - device=0, - do_sample=False, - max_new_tokens=self.config.cerebras.max_length, - no_repeat_ngram_size=self.config.cerebras.no_repeat_ngram_size, - ) - - def backward( - self, messages: List[Dict[str, str]], response: str, rewards: FloatTensor - ) -> str: - pass - - @staticmethod - def _process_history(history: List[Dict[str, str]]) -> str: - processed_history = "" - for message in history: - if message["role"] == "system": - processed_history += "system: " + message["content"] + "\n" - if message["role"] == "assistant": - processed_history += "assistant: " + message["content"] + "\n" - if message["role"] == "user": - processed_history += "user: " + message["content"] + "\n" - return processed_history - - def forward(self, messages: List[Dict[str, str]]) -> str: - history = self._process_history(messages) - return ( - self.pipe(history)[0]["generated_text"] - .split(":")[-1] - .replace(str(history), "") - ) - - -if __name__ == "__main__": - bittensor.utils.version_checking() - CerebrasMiner().run() diff --git a/neurons/text/prompting/miners/cohere/README.md b/neurons/text/prompting/miners/cohere/README.md deleted file mode 100644 index 03f971646a..0000000000 --- a/neurons/text/prompting/miners/cohere/README.md +++ /dev/null @@ -1,128 +0,0 @@ -# Cohere Miner -This repository contains the implementation of a language model server using the Cohere API. The model is integrated into the Bittensor network, allowing it to serve as a Bittensor neuron. - -# Example Usage -``` -python3 -m pip install -r neurons/text/prompting/miners/cohere/requirements.txt -python3 neurons/text/prompting/miners/cohere/neuron.py --cohere.api_key -``` - -# Full Usage -``` -usage: neuron.py [-h] [--cohere.model_name COHERE.MODEL_NAME] [--cohere.max_tokens COHERE.MAX_TOKENS] - [--cohere.temperature COHERE.TEMPERATURE] [--cohere.k COHERE.K] [--cohere.p COHERE.P] - [--cohere.frequency_penalty COHERE.FREQUENCY_PENALTY] [--cohere.presence_penalty COHERE.PRESENCE_PENALTY] - [--cohere.truncate COHERE.TRUNCATE] [--cohere.stop COHERE.STOP] --cohere.api_key COHERE.API_KEY [--netuid NETUID] - [--neuron.name NEURON.NAME] [--neuron.blocks_per_epoch NEURON.BLOCKS_PER_EPOCH] [--neuron.no_set_weights] - [--neuron.max_batch_size NEURON.MAX_BATCH_SIZE] [--neuron.max_sequence_len NEURON.MAX_SEQUENCE_LEN] - [--neuron.blacklist.hotkeys [NEURON.BLACKLIST.HOTKEYS ...]] [--neuron.blacklist.allow_non_registered] - [--neuron.blacklist.default_stake NEURON.BLACKLIST.DEFAULT_STAKE] [--neuron.default_priority NEURON.DEFAULT_PRIORITY] - [--wallet.name WALLET.NAME] [--wallet.hotkey WALLET.HOTKEY] [--wallet.path WALLET.PATH] [--wallet._mock] - [--wallet.reregister WALLET.REREGISTER] [--axon.priority.max_workers AXON.PRIORITY.MAX_WORKERS] - [--axon.priority.maxsize AXON.PRIORITY.MAXSIZE] [--axon.port AXON.PORT] [--axon.ip AXON.IP] - [--axon.external_port AXON.EXTERNAL_PORT] [--axon.external_ip AXON.EXTERNAL_IP] [--axon.max_workers AXON.MAX_WORKERS] - [--axon.maximum_concurrent_rpcs AXON.MAXIMUM_CONCURRENT_RPCS] [--subtensor.network SUBTENSOR.NETWORK] - [--subtensor.chain_endpoint SUBTENSOR.CHAIN_ENDPOINT] [--subtensor._mock] - [--subtensor.register.num_processes SUBTENSOR.REGISTER.NUM_PROCESSES] - [--subtensor.register.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL] [--subtensor.register.no_output_in_place] - [--subtensor.register.verbose] [--subtensor.register.cuda.use_cuda] [--subtensor.register.cuda.no_cuda] - [--subtensor.register.cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...]] - [--subtensor.register.cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB] [--logging.debug] [--logging.trace] [--logging.record_log] - [--logging.logging_dir LOGGING.LOGGING_DIR] [--metagraph._mock] [--config CONFIG] [--strict] - -optional arguments: - -h, --help show this help message and exit - --cohere.model_name COHERE.MODEL_NAME - Name of the model. - --cohere.max_tokens COHERE.MAX_TOKENS - Number of tokens to generate. - --cohere.temperature COHERE.TEMPERATURE - Temperature of generation. - --cohere.k COHERE.K Number of most likely tokens to consider at each step. - --cohere.p COHERE.P Total probability mass of tokens to consider at each step. - --cohere.frequency_penalty COHERE.FREQUENCY_PENALTY - Penalizes repeated tokens according to frequency. - --cohere.presence_penalty COHERE.PRESENCE_PENALTY - Penalizes repeated tokens. - --cohere.truncate COHERE.TRUNCATE - Specify how the client handles inputs longer than the maximum token length: Truncate from START, END or NONE - --cohere.stop COHERE.STOP - List of tokens to stop generation on. - --cohere.api_key COHERE.API_KEY - API key for Cohere. - --netuid NETUID Subnet netuid - --neuron.name NEURON.NAME - Trials for this miner go in miner.root / (wallet_cold - wallet_hot) / miner.name - --neuron.blocks_per_epoch NEURON.BLOCKS_PER_EPOCH - Blocks until the miner sets weights on chain - --neuron.no_set_weights - If True, the model does not set weights. - --neuron.max_batch_size NEURON.MAX_BATCH_SIZE - The maximum batch size for forward requests. - --neuron.max_sequence_len NEURON.MAX_SEQUENCE_LEN - The maximum sequence length for forward requests. - --neuron.blacklist.hotkeys [NEURON.BLACKLIST.HOTKEYS ...] - To blacklist certain hotkeys - --neuron.blacklist.allow_non_registered - If True, the miner will allow non-registered hotkeys to mine. - --neuron.blacklist.default_stake NEURON.BLACKLIST.DEFAULT_STAKE - Set default stake for miners. - --neuron.default_priority NEURON.DEFAULT_PRIORITY - Set default priority for miners. - --wallet.name WALLET.NAME - The name of the wallet to unlock for running bittensor (name mock is reserved for mocking this wallet) - --wallet.hotkey WALLET.HOTKEY - The name of wallet's hotkey. - --wallet.path WALLET.PATH - The path to your bittensor wallets - --wallet._mock To turn on wallet mocking for testing purposes. - --wallet.reregister WALLET.REREGISTER - Whether to reregister the wallet if it is not already registered. - --axon.priority.max_workers AXON.PRIORITY.MAX_WORKERS - maximum number of threads in thread pool - --axon.priority.maxsize AXON.PRIORITY.MAXSIZE - maximum size of tasks in priority queue - --axon.port AXON.PORT - The local port this axon endpoint is bound to. i.e. 8091 - --axon.ip AXON.IP The local ip this axon binds to. ie. [::] - --axon.external_port AXON.EXTERNAL_PORT - The public port this axon broadcasts to the network. i.e. 8091 - --axon.external_ip AXON.EXTERNAL_IP - The external ip this axon broadcasts to the network to. ie. [::] - --axon.max_workers AXON.MAX_WORKERS - The maximum number connection handler threads working simultaneously on this endpoint. The grpc server distributes - new worker threads to service requests up to this number. - --axon.maximum_concurrent_rpcs AXON.MAXIMUM_CONCURRENT_RPCS - Maximum number of allowed active connections - --subtensor.network SUBTENSOR.NETWORK - The subtensor network flag. The likely choices are: -- finney (main network) -- local (local running network) -- - mock (creates a mock connection (for testing)) If this option is set it overloads subtensor.chain_endpoint with an - entry point node from that network. - --subtensor.chain_endpoint SUBTENSOR.CHAIN_ENDPOINT - The subtensor endpoint flag. If set, overrides the --network flag. - --subtensor._mock To turn on subtensor mocking for testing purposes. - --subtensor.register.num_processes SUBTENSOR.REGISTER.NUM_PROCESSES, -n SUBTENSOR.REGISTER.NUM_PROCESSES - Number of processors to use for registration - --subtensor.register.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, --subtensor.register.cuda.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, --cuda.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, -u SUBTENSOR.REGISTER.UPDATE_INTERVAL - The number of nonces to process before checking for next block during registration - --subtensor.register.no_output_in_place, --no_output_in_place - Whether to not ouput the registration statistics in-place. Set flag to disable output in-place. - --subtensor.register.verbose - Whether to ouput the registration statistics verbosely. - --subtensor.register.cuda.use_cuda, --cuda, --cuda.use_cuda - Set flag to use CUDA to register. - --subtensor.register.cuda.no_cuda, --no_cuda, --cuda.no_cuda - Set flag to not use CUDA for registration - --subtensor.register.cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...], --cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...] - Set the CUDA device id(s). Goes by the order of speed. (i.e. 0 is the fastest). - --subtensor.register.cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB, --cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB - Set the number of Threads Per Block for CUDA. - --logging.debug Turn on bittensor debugging information - --logging.trace Turn on bittensor trace level information - --logging.record_log Turns on logging to file. - --logging.logging_dir LOGGING.LOGGING_DIR - Logging default root directory. - --metagraph._mock To turn on metagraph mocking for testing purposes. - --config CONFIG If set, defaults are overridden by passed file. - --strict If flagged, config will check that only exact arguemnts have been set. -``` \ No newline at end of file diff --git a/neurons/text/prompting/miners/cohere/neuron.py b/neurons/text/prompting/miners/cohere/neuron.py deleted file mode 100644 index f619c555b4..0000000000 --- a/neurons/text/prompting/miners/cohere/neuron.py +++ /dev/null @@ -1,135 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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. - -# General. -import json -import argparse -import bittensor -from typing import List, Dict -from langchain.llms import Cohere -from torch import FloatTensor - - -class CohereMiner(bittensor.BasePromptingMiner): - @classmethod - def check_config(cls, config: "bittensor.Config"): - assert ( - config.cohere.api_key != None - ), "the miner requires passing --cohere.api_key as an argument of the config." - - @classmethod - def add_args(cls, parser: argparse.ArgumentParser): - parser.add_argument( - "--cohere.model_name", - type=str, - help="Name of the model.", - default="command-xlarge-nightly", - ) - parser.add_argument( - "--cohere.max_tokens", - type=int, - help="Number of tokens to generate.", - default=256, - ) - parser.add_argument( - "--cohere.temperature", - type=float, - help="Temperature of generation.", - default=0.75, - ) - parser.add_argument( - "--cohere.k", - type=int, - help="Number of most likely tokens to consider at each step.", - default=0, - ) - parser.add_argument( - "--cohere.p", - type=int, - help="Total probability mass of tokens to consider at each step.", - default=1, - ) - parser.add_argument( - "--cohere.frequency_penalty", - type=float, - help="Penalizes repeated tokens according to frequency.", - default=0.0, - ) - parser.add_argument( - "--cohere.presence_penalty", - type=float, - help="Penalizes repeated tokens.", - default=0.0, - ) - parser.add_argument( - "--cohere.truncate", - type=str, - help="Specify how the client handles inputs longer than the maximum token length: Truncate from START, END or NONE", - default=None, - ) - parser.add_argument( - "--cohere.stop", - type=str, - help="List of tokens to stop generation on.", - default=None, - ) - parser.add_argument( - "--cohere.api_key", type=str, help="API key for Cohere.", required=True - ) - - def __init__(self): - super(CohereMiner, self).__init__() - print(self.config) - - self.model = Cohere( - model=self.config.cohere.model_name, - cohere_api_key=self.config.cohere.api_key, - max_tokens=self.config.cohere.max_tokens, - temperature=self.config.cohere.temperature, - k=self.config.cohere.k, - p=self.config.cohere.p, - frequency_penalty=self.config.cohere.frequency_penalty, - presence_penalty=self.config.cohere.presence_penalty, - truncate=self.config.cohere.truncate, - stop=self.config.cohere.stop, - ) - - def backward( - self, messages: List[Dict[str, str]], response: str, rewards: FloatTensor - ) -> str: - pass - - @staticmethod - def _process_history(history: List[Dict[str, str]]) -> str: - processed_history = "" - for message in history: - if message["role"] == "system": - processed_history += "system: " + message["content"] + "\n" - if message["role"] == "assistant": - processed_history += "assistant: " + message["content"] + "\n" - if message["role"] == "user": - processed_history += "user: " + message["content"] + "\n" - return processed_history - - def forward(self, messages: List[Dict[str, str]]) -> str: - history = self._process_history(messages) - return self.model(history) - - -if __name__ == "__main__": - bittensor.utils.version_checking() - CohereMiner().run() diff --git a/neurons/text/prompting/miners/cohere/requirements.txt b/neurons/text/prompting/miners/cohere/requirements.txt deleted file mode 100644 index 889f2e0a9c..0000000000 --- a/neurons/text/prompting/miners/cohere/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -cohere \ No newline at end of file diff --git a/neurons/text/prompting/miners/gooseai/README.md b/neurons/text/prompting/miners/gooseai/README.md deleted file mode 100644 index 55c4ae8e42..0000000000 --- a/neurons/text/prompting/miners/gooseai/README.md +++ /dev/null @@ -1,149 +0,0 @@ -# GooseAI Bittensor Miner -This repository contains a Bittensor Miner that uses GooseAI's endpoint. The miner connects to the Bittensor network, registers its wallet, and serves a GooseAI model to the network. - -## Prerequisites - -- Python 3.8+ -- langchain - -## Installation - -1. Clone the repository -2. Install the required packages with `pip install -r requirements.txt` -3. Set your GooseAI API key in the `api_key` argument when running the script - -For more configuration options related to the wallet, axon, subtensor, logging, and metagraph, please refer to the Bittensor documentation. - -## Example Usage - -To run the GooseAI Bittensor Miner with default settings, use the following command: - -``` -python3 -m pip install -r neurons/text/prompting/miners/gooseai/requirements.txt -python3 neurons/text/prompting/miners/gooseai/neuron.py --gooseai.api_key -``` - -# Full Usage -``` -usage: neuron.py [-h] --gooseai.api_key GOOSEAI.API_KEY [--gooseai.model_name GOOSEAI.MODEL_NAME] - [--gooseai.temperature GOOSEAI.TEMPERATURE] [--gooseai.max_tokens GOOSEAI.MAX_TOKENS] [--gooseai.top_p GOOSEAI.TOP_P] - [--gooseai.min_tokens GOOSEAI.MIN_TOKENS] [--gooseai.frequency_penalty GOOSEAI.FREQUENCY_PENALTY] - [--gooseai.presence_penalty GOOSEAI.PRESENCE_PENALTY] [--gooseai.n GOOSEAI.N] [--gooseai.model_kwargs GOOSEAI.MODEL_KWARGS] - [--gooseai.logit_bias GOOSEAI.LOGIT_BIAS] [--netuid NETUID] [--neuron.name NEURON.NAME] - [--neuron.blocks_per_epoch NEURON.BLOCKS_PER_EPOCH] [--neuron.no_set_weights] - [--neuron.max_batch_size NEURON.MAX_BATCH_SIZE] [--neuron.max_sequence_len NEURON.MAX_SEQUENCE_LEN] - [--neuron.blacklist.hotkeys [NEURON.BLACKLIST.HOTKEYS ...]] [--neuron.blacklist.allow_non_registered] - [--neuron.blacklist.default_stake NEURON.BLACKLIST.DEFAULT_STAKE] [--neuron.default_priority NEURON.DEFAULT_PRIORITY] - [--wallet.name WALLET.NAME] [--wallet.hotkey WALLET.HOTKEY] [--wallet.path WALLET.PATH] [--wallet._mock] - [--wallet.reregister WALLET.REREGISTER] [--axon.priority.max_workers AXON.PRIORITY.MAX_WORKERS] - [--axon.priority.maxsize AXON.PRIORITY.MAXSIZE] [--axon.port AXON.PORT] [--axon.ip AXON.IP] - [--axon.external_port AXON.EXTERNAL_PORT] [--axon.external_ip AXON.EXTERNAL_IP] [--axon.max_workers AXON.MAX_WORKERS] - [--axon.maximum_concurrent_rpcs AXON.MAXIMUM_CONCURRENT_RPCS] [--subtensor.network SUBTENSOR.NETWORK] - [--subtensor.chain_endpoint SUBTENSOR.CHAIN_ENDPOINT] [--subtensor._mock] - [--subtensor.register.num_processes SUBTENSOR.REGISTER.NUM_PROCESSES] - [--subtensor.register.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL] [--subtensor.register.no_output_in_place] - [--subtensor.register.verbose] [--subtensor.register.cuda.use_cuda] [--subtensor.register.cuda.no_cuda] - [--subtensor.register.cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...]] - [--subtensor.register.cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB] [--logging.debug] [--logging.trace] [--logging.record_log] - [--logging.logging_dir LOGGING.LOGGING_DIR] [--metagraph._mock] [--config CONFIG] [--strict] - -optional arguments: - -h, --help show this help message and exit - --gooseai.api_key GOOSEAI.API_KEY - GooseAI api key required. - --gooseai.model_name GOOSEAI.MODEL_NAME - Model name to use - --gooseai.temperature GOOSEAI.TEMPERATURE - What sampling temperature to use - --gooseai.max_tokens GOOSEAI.MAX_TOKENS - The maximum number of tokens to generate in the completion - --gooseai.top_p GOOSEAI.TOP_P - Total probability mass of tokens to consider at each step - --gooseai.min_tokens GOOSEAI.MIN_TOKENS - The minimum number of tokens to generate in the completion - --gooseai.frequency_penalty GOOSEAI.FREQUENCY_PENALTY - Penalizes repeated tokens according to frequency - --gooseai.presence_penalty GOOSEAI.PRESENCE_PENALTY - Penalizes repeated tokens - --gooseai.n GOOSEAI.N - How many completions to generate for each prompt - --gooseai.model_kwargs GOOSEAI.MODEL_KWARGS - Holds any model parameters valid for `create` call not explicitly specified - --gooseai.logit_bias GOOSEAI.LOGIT_BIAS - Adjust the probability of specific tokens being generated - --netuid NETUID Subnet netuid - --neuron.name NEURON.NAME - Trials for this miner go in miner.root / (wallet_cold - wallet_hot) / miner.name - --neuron.blocks_per_epoch NEURON.BLOCKS_PER_EPOCH - Blocks until the miner sets weights on chain - --neuron.no_set_weights - If True, the model does not set weights. - --neuron.max_batch_size NEURON.MAX_BATCH_SIZE - The maximum batch size for forward requests. - --neuron.max_sequence_len NEURON.MAX_SEQUENCE_LEN - The maximum sequence length for forward requests. - --neuron.blacklist.hotkeys [NEURON.BLACKLIST.HOTKEYS ...] - To blacklist certain hotkeys - --neuron.blacklist.allow_non_registered - If True, the miner will allow non-registered hotkeys to mine. - --neuron.blacklist.default_stake NEURON.BLACKLIST.DEFAULT_STAKE - Set default stake for miners. - --neuron.default_priority NEURON.DEFAULT_PRIORITY - Set default priority for miners. - --wallet.name WALLET.NAME - The name of the wallet to unlock for running bittensor (name mock is reserved for mocking this wallet) - --wallet.hotkey WALLET.HOTKEY - The name of wallet's hotkey. - --wallet.path WALLET.PATH - The path to your bittensor wallets - --wallet._mock To turn on wallet mocking for testing purposes. - --wallet.reregister WALLET.REREGISTER - Whether to reregister the wallet if it is not already registered. - --axon.priority.max_workers AXON.PRIORITY.MAX_WORKERS - maximum number of threads in thread pool - --axon.priority.maxsize AXON.PRIORITY.MAXSIZE - maximum size of tasks in priority queue - --axon.port AXON.PORT - The local port this axon endpoint is bound to. i.e. 8091 - --axon.ip AXON.IP The local ip this axon binds to. ie. [::] - --axon.external_port AXON.EXTERNAL_PORT - The public port this axon broadcasts to the network. i.e. 8091 - --axon.external_ip AXON.EXTERNAL_IP - The external ip this axon broadcasts to the network to. ie. [::] - --axon.max_workers AXON.MAX_WORKERS - The maximum number connection handler threads working simultaneously on this endpoint. The grpc server distributes - new worker threads to service requests up to this number. - --axon.maximum_concurrent_rpcs AXON.MAXIMUM_CONCURRENT_RPCS - Maximum number of allowed active connections - --subtensor.network SUBTENSOR.NETWORK - The subtensor network flag. The likely choices are: -- finney (main network) -- local (local running network) -- - mock (creates a mock connection (for testing)) If this option is set it overloads subtensor.chain_endpoint with an - entry point node from that network. - --subtensor.chain_endpoint SUBTENSOR.CHAIN_ENDPOINT - The subtensor endpoint flag. If set, overrides the --network flag. - --subtensor._mock To turn on subtensor mocking for testing purposes. - --subtensor.register.num_processes SUBTENSOR.REGISTER.NUM_PROCESSES, -n SUBTENSOR.REGISTER.NUM_PROCESSES - Number of processors to use for registration - --subtensor.register.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, --subtensor.register.cuda.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, --cuda.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, -u SUBTENSOR.REGISTER.UPDATE_INTERVAL - The number of nonces to process before checking for next block during registration - --subtensor.register.no_output_in_place, --no_output_in_place - Whether to not ouput the registration statistics in-place. Set flag to disable output in-place. - --subtensor.register.verbose - Whether to ouput the registration statistics verbosely. - --subtensor.register.cuda.use_cuda, --cuda, --cuda.use_cuda - Set flag to use CUDA to register. - --subtensor.register.cuda.no_cuda, --no_cuda, --cuda.no_cuda - Set flag to not use CUDA for registration - --subtensor.register.cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...], --cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...] - Set the CUDA device id(s). Goes by the order of speed. (i.e. 0 is the fastest). - --subtensor.register.cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB, --cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB - Set the number of Threads Per Block for CUDA. - --logging.debug Turn on bittensor debugging information - --logging.trace Turn on bittensor trace level information - --logging.record_log Turns on logging to file. - --logging.logging_dir LOGGING.LOGGING_DIR - Logging default root directory. - --metagraph._mock To turn on metagraph mocking for testing purposes. - --config CONFIG If set, defaults are overridden by passed file. - --strict If flagged, config will check that only exact arguemnts have been set. -``` \ No newline at end of file diff --git a/neurons/text/prompting/miners/gooseai/neuron.py b/neurons/text/prompting/miners/gooseai/neuron.py deleted file mode 100644 index 893f0aebb6..0000000000 --- a/neurons/text/prompting/miners/gooseai/neuron.py +++ /dev/null @@ -1,143 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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 argparse -import bittensor -from typing import List, Dict, Any, Optional -from torch import FloatTensor - -from langchain.llms import GooseAI - - -class GooseAIMiner(bittensor.BasePromptingMiner): - @classmethod - def check_config(cls, config: "bittensor.Config"): - pass - - @classmethod - def add_args(cls, parser: argparse.ArgumentParser): - parser.add_argument( - "--gooseai.api_key", - type=str, - required=True, - help="GooseAI api key required.", - ) - parser.add_argument( - "--gooseai.model_name", - type=str, - default="gpt-neo-20b", - help="Model name to use", - ) - parser.add_argument( - "--gooseai.temperature", - type=float, - default=0.7, - help="What sampling temperature to use", - ) - parser.add_argument( - "--gooseai.max_tokens", - type=int, - default=256, - help="The maximum number of tokens to generate in the completion", - ) - parser.add_argument( - "--gooseai.top_p", - type=float, - default=1, - help="Total probability mass of tokens to consider at each step", - ) - parser.add_argument( - "--gooseai.min_tokens", - type=int, - default=1, - help="The minimum number of tokens to generate in the completion", - ) - parser.add_argument( - "--gooseai.frequency_penalty", - type=float, - default=0, - help="Penalizes repeated tokens according to frequency", - ) - parser.add_argument( - "--gooseai.presence_penalty", - type=float, - default=0, - help="Penalizes repeated tokens", - ) - parser.add_argument( - "--gooseai.n", - type=int, - default=1, - help="How many completions to generate for each prompt", - ) - parser.add_argument( - "--gooseai.model_kwargs", - type=Dict[str, Any], - default=dict(), - help="Holds any model parameters valid for `create` call not explicitly specified", - ) - parser.add_argument( - "--gooseai.logit_bias", - type=Optional[Dict[str, float]], - default=dict(), - help="Adjust the probability of specific tokens being generated", - ) - - def __init__(self): - super(GooseAIMiner, self).__init__() - print(self.config) - model_kwargs = { - "model": self.config.gooseai.model_name, - "n_ctx": self.config.gooseai.max_tokens, - "n_parts": self.config.gooseai.n, - "temp": self.config.gooseai.temperature, - "top_p": self.config.gooseai.top_p, - "repeat_penalty": self.config.gooseai.frequency_penalty, - } - self.model = GooseAI( - gooseai_api_key=self.config.gooseai.api_key, model_kwargs=model_kwargs - ) - - def backward( - self, messages: List[Dict[str, str]], response: str, rewards: FloatTensor - ) -> str: - pass - - @staticmethod - def _process_history(history: List[dict]) -> str: - processed_history = "" - for message in history: - if message["role"] == "system": - processed_history += "system: " + message["content"] + "\n" - if message["role"] == "assistant": - processed_history += "assistant: " + message["content"] + "\n" - if message["role"] == "user": - processed_history += "user: " + message["content"] + "\n" - return processed_history - - def forward(self, messages: List[Dict[str, str]]) -> str: - bittensor.logging.info("messages", str(messages)) - history = self._process_history(messages) - bittensor.logging.info("history", str(history)) - resp = self.model(history) - bittensor.logging.info("response", str(resp)) - return resp - - -if __name__ == "__main__": - bittensor.utils.version_checking() - GooseAIMiner().run() diff --git a/neurons/text/prompting/miners/gooseai/requirements.txt b/neurons/text/prompting/miners/gooseai/requirements.txt deleted file mode 100644 index e7f08b2136..0000000000 --- a/neurons/text/prompting/miners/gooseai/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -langchain \ No newline at end of file diff --git a/neurons/text/prompting/miners/gpt4all/README.md b/neurons/text/prompting/miners/gpt4all/README.md deleted file mode 100644 index c9c876d548..0000000000 --- a/neurons/text/prompting/miners/gpt4all/README.md +++ /dev/null @@ -1,162 +0,0 @@ -## GPT4ALL Miner -GPT4ALL prompting miner for bittensor - -# Example Usage -``` -python3 -m pip install -r neurons/text/prompting/miners/gpt4all/requirements.txt -python3 neurons/text/prompting/miners/gpt4all/neuron.py -``` - -# Obtaining (converted) [GPT4All](https://github.com/nomic-ai/gpt4all) weights - -- Obtain the `gpt4all-lora-quantized.bin` model -- It is distributed in the old `ggml` format which is now obsoleted -- You have to convert it to the new format using [./convert-gpt4all-to-ggml.py](https://github.com/ggerganov/llama.cpp/blob/master/convert-gpt4all-to-ggml.py). You may also need to -convert the model from the old format to the new format with [./migrate-ggml-2023-03-30-pr613.py](https://github.com/ggerganov/llama.cpp/blob/master/migrate-ggml-2023-03-30-pr613.py): - - ```bash - python3 convert-gpt4all-to-ggml.py models/gpt4all-7B/gpt4all-lora-quantized.bin ./models/tokenizer.model - python3 migrate-ggml-2023-03-30-pr613.py models/gpt4all-7B/gpt4all-lora-quantized.bin models/gpt4all-7B/gpt4all-lora-quantized-new.bin - ``` - -- You can now use the newly generated `gpt4all-lora-quantized-new.bin` model in exactly the same way as all other models -- The original model is saved in the same folder with a suffix `.orig` -- Tokenizer can be found [here](https://huggingface.co/decapoda-research/llama-7b-hf/blob/main/tokenizer.model) - - -# Full Usage -``` -usage: neuron.py [-h] --gpt4all.model GPT4ALL.MODEL [--gpt4all.n_ctx GPT4ALL.N_CTX] [--gpt4all.n_parts GPT4ALL.N_PARTS] - [--gpt4all.seed GPT4ALL.SEED] [--gpt4all.f16_kv] [--gpt4all.logits_all] [--gpt4all.vocab_only] [--gpt4all.use_mlock] - [--gpt4all.embedding] [--gpt4all.n_threads GPT4ALL.N_THREADS] [--gpt4all.n_predict GPT4ALL.N_PREDICT] - [--gpt4all.temp GPT4ALL.TEMP] [--gpt4all.top_p GPT4ALL.TOP_P] [--gpt4all.top_k GPT4ALL.TOP_K] [--gpt4all.echo] - [--gpt4all.stop GPT4ALL.STOP] [--gpt4all.repeat_last_n GPT4ALL.REPEAT_LAST_N] - [--gpt4all.repeat_penalty GPT4ALL.REPEAT_PENALTY] [--gpt4all.n_batch GPT4ALL.N_BATCH] [--gpt4all.streaming] - [--netuid NETUID] [--neuron.name NEURON.NAME] [--neuron.blocks_per_epoch NEURON.BLOCKS_PER_EPOCH] [--neuron.no_set_weights] - [--neuron.max_batch_size NEURON.MAX_BATCH_SIZE] [--neuron.max_sequence_len NEURON.MAX_SEQUENCE_LEN] - [--neuron.blacklist.hotkeys [NEURON.BLACKLIST.HOTKEYS ...]] [--neuron.blacklist.allow_non_registered] - [--neuron.blacklist.default_stake NEURON.BLACKLIST.DEFAULT_STAKE] [--neuron.default_priority NEURON.DEFAULT_PRIORITY] - [--wallet.name WALLET.NAME] [--wallet.hotkey WALLET.HOTKEY] [--wallet.path WALLET.PATH] [--wallet._mock] - [--wallet.reregister WALLET.REREGISTER] [--axon.priority.max_workers AXON.PRIORITY.MAX_WORKERS] - [--axon.priority.maxsize AXON.PRIORITY.MAXSIZE] [--axon.port AXON.PORT] [--axon.ip AXON.IP] - [--axon.external_port AXON.EXTERNAL_PORT] [--axon.external_ip AXON.EXTERNAL_IP] [--axon.max_workers AXON.MAX_WORKERS] - [--axon.maximum_concurrent_rpcs AXON.MAXIMUM_CONCURRENT_RPCS] [--subtensor.network SUBTENSOR.NETWORK] - [--subtensor.chain_endpoint SUBTENSOR.CHAIN_ENDPOINT] [--subtensor._mock] - [--subtensor.register.num_processes SUBTENSOR.REGISTER.NUM_PROCESSES] - [--subtensor.register.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL] [--subtensor.register.no_output_in_place] - [--subtensor.register.verbose] [--subtensor.register.cuda.use_cuda] [--subtensor.register.cuda.no_cuda] - [--subtensor.register.cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...]] - [--subtensor.register.cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB] [--logging.debug] [--logging.trace] [--logging.record_log] - [--logging.logging_dir LOGGING.LOGGING_DIR] [--metagraph._mock] [--config CONFIG] [--strict] - -optional arguments: - -h, --help show this help message and exit - --gpt4all.model GPT4ALL.MODEL - Path to pretrained gpt4all model in ggml format. - --gpt4all.n_ctx GPT4ALL.N_CTX - Token context window. - --gpt4all.n_parts GPT4ALL.N_PARTS - Number of parts to split the model into. If -1, the number of parts is automatically determined. - --gpt4all.seed GPT4ALL.SEED - Seed. If -1, a random seed is used. - --gpt4all.f16_kv Use half-precision for key/value cache. - --gpt4all.logits_all Return logits for all tokens, not just the last token. - --gpt4all.vocab_only Only load the vocabulary, no weights. - --gpt4all.use_mlock Force system to keep model in RAM. - --gpt4all.embedding Use embedding mode only. - --gpt4all.n_threads GPT4ALL.N_THREADS - Number of threads to use. - --gpt4all.n_predict GPT4ALL.N_PREDICT - The maximum number of tokens to generate. - --gpt4all.temp GPT4ALL.TEMP - The temperature to use for sampling. - --gpt4all.top_p GPT4ALL.TOP_P - The top-p value to use for sampling. - --gpt4all.top_k GPT4ALL.TOP_K - The top-k value to use for sampling. - --gpt4all.echo Whether to echo the prompt. - --gpt4all.stop GPT4ALL.STOP - Stop tokens. - --gpt4all.repeat_last_n GPT4ALL.REPEAT_LAST_N - Last n tokens to penalize. - --gpt4all.repeat_penalty GPT4ALL.REPEAT_PENALTY - The penalty to apply to repeated tokens. - --gpt4all.n_batch GPT4ALL.N_BATCH - Batch size for prompt processing. - --gpt4all.streaming Whether to stream the results or not. - --netuid NETUID Subnet netuid - --neuron.name NEURON.NAME - Trials for this miner go in miner.root / (wallet_cold - wallet_hot) / miner.name - --neuron.blocks_per_epoch NEURON.BLOCKS_PER_EPOCH - Blocks until the miner sets weights on chain - --neuron.no_set_weights - If True, the model does not set weights. - --neuron.max_batch_size NEURON.MAX_BATCH_SIZE - The maximum batch size for forward requests. - --neuron.max_sequence_len NEURON.MAX_SEQUENCE_LEN - The maximum sequence length for forward requests. - --neuron.blacklist.hotkeys [NEURON.BLACKLIST.HOTKEYS ...] - To blacklist certain hotkeys - --neuron.blacklist.allow_non_registered - If True, the miner will allow non-registered hotkeys to mine. - --neuron.blacklist.default_stake NEURON.BLACKLIST.DEFAULT_STAKE - Set default stake for miners. - --neuron.default_priority NEURON.DEFAULT_PRIORITY - Set default priority for miners. - --wallet.name WALLET.NAME - The name of the wallet to unlock for running bittensor (name mock is reserved for mocking this wallet) - --wallet.hotkey WALLET.HOTKEY - The name of wallet's hotkey. - --wallet.path WALLET.PATH - The path to your bittensor wallets - --wallet._mock To turn on wallet mocking for testing purposes. - --wallet.reregister WALLET.REREGISTER - Whether to reregister the wallet if it is not already registered. - --axon.priority.max_workers AXON.PRIORITY.MAX_WORKERS - maximum number of threads in thread pool - --axon.priority.maxsize AXON.PRIORITY.MAXSIZE - maximum size of tasks in priority queue - --axon.port AXON.PORT - The local port this axon endpoint is bound to. i.e. 8091 - --axon.ip AXON.IP The local ip this axon binds to. ie. [::] - --axon.external_port AXON.EXTERNAL_PORT - The public port this axon broadcasts to the network. i.e. 8091 - --axon.external_ip AXON.EXTERNAL_IP - The external ip this axon broadcasts to the network to. ie. [::] - --axon.max_workers AXON.MAX_WORKERS - The maximum number connection handler threads working simultaneously on this endpoint. The grpc server distributes - new worker threads to service requests up to this number. - --axon.maximum_concurrent_rpcs AXON.MAXIMUM_CONCURRENT_RPCS - Maximum number of allowed active connections - --subtensor.network SUBTENSOR.NETWORK - The subtensor network flag. The likely choices are: -- finney (main network) -- local (local running network) -- - mock (creates a mock connection (for testing)) If this option is set it overloads subtensor.chain_endpoint with an - entry point node from that network. - --subtensor.chain_endpoint SUBTENSOR.CHAIN_ENDPOINT - The subtensor endpoint flag. If set, overrides the --network flag. - --subtensor._mock To turn on subtensor mocking for testing purposes. - --subtensor.register.num_processes SUBTENSOR.REGISTER.NUM_PROCESSES, -n SUBTENSOR.REGISTER.NUM_PROCESSES - Number of processors to use for registration - --subtensor.register.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, --subtensor.register.cuda.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, --cuda.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, -u SUBTENSOR.REGISTER.UPDATE_INTERVAL - The number of nonces to process before checking for next block during registration - --subtensor.register.no_output_in_place, --no_output_in_place - Whether to not ouput the registration statistics in-place. Set flag to disable output in-place. - --subtensor.register.verbose - Whether to ouput the registration statistics verbosely. - --subtensor.register.cuda.use_cuda, --cuda, --cuda.use_cuda - Set flag to use CUDA to register. - --subtensor.register.cuda.no_cuda, --no_cuda, --cuda.no_cuda - Set flag to not use CUDA for registration - --subtensor.register.cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...], --cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...] - Set the CUDA device id(s). Goes by the order of speed. (i.e. 0 is the fastest). - --subtensor.register.cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB, --cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB - Set the number of Threads Per Block for CUDA. - --logging.debug Turn on bittensor debugging information - --logging.trace Turn on bittensor trace level information - --logging.record_log Turns on logging to file. - --logging.logging_dir LOGGING.LOGGING_DIR - Logging default root directory. - --metagraph._mock To turn on metagraph mocking for testing purposes. - --config CONFIG If set, defaults are overridden by passed file. - --strict If flagged, config will check that only exact arguemnts have been set. -``` \ No newline at end of file diff --git a/neurons/text/prompting/miners/gpt4all/neuron.py b/neurons/text/prompting/miners/gpt4all/neuron.py deleted file mode 100644 index 0b98090295..0000000000 --- a/neurons/text/prompting/miners/gpt4all/neuron.py +++ /dev/null @@ -1,195 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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 argparse -import bittensor -from typing import List, Dict -from torch import FloatTensor -from langchain.llms import GPT4All - - -class GPT4ALLMiner(bittensor.BasePromptingMiner): - @classmethod - def check_config(cls, config: "bittensor.Config"): - pass - - @classmethod - def add_args(cls, parser: argparse.ArgumentParser): - parser.add_argument( - "--gpt4all.model", - type=str, - help="Path to pretrained gpt4all model in ggml format.", - required=True, - ) - parser.add_argument( - "--gpt4all.n_ctx", type=int, default=512, help="Token context window." - ) - parser.add_argument( - "--gpt4all.n_parts", - type=int, - default=-1, - help="Number of parts to split the model into. If -1, the number of parts is automatically determined.", - ) - parser.add_argument( - "--gpt4all.seed", - type=int, - default=0, - help="Seed. If -1, a random seed is used.", - ) - parser.add_argument( - "--gpt4all.f16_kv", - action="store_true", - default=False, - help="Use half-precision for key/value cache.", - ) - parser.add_argument( - "--gpt4all.logits_all", - action="store_true", - default=False, - help="Return logits for all tokens, not just the last token.", - ) - parser.add_argument( - "--gpt4all.vocab_only", - action="store_true", - default=False, - help="Only load the vocabulary, no weights.", - ) - parser.add_argument( - "--gpt4all.use_mlock", - action="store_true", - default=False, - help="Force system to keep model in RAM.", - ) - parser.add_argument( - "--gpt4all.embedding", - action="store_true", - default=False, - help="Use embedding mode only.", - ) - parser.add_argument( - "--gpt4all.n_threads", type=int, default=4, help="Number of threads to use." - ) - parser.add_argument( - "--gpt4all.n_predict", - type=int, - default=256, - help="The maximum number of tokens to generate.", - ) - parser.add_argument( - "--gpt4all.temp", - type=float, - default=0.8, - help="The temperature to use for sampling.", - ) - parser.add_argument( - "--gpt4all.top_p", - type=float, - default=0.95, - help="The top-p value to use for sampling.", - ) - parser.add_argument( - "--gpt4all.top_k", - type=int, - default=40, - help="The top-k value to use for sampling.", - ) - parser.add_argument( - "--gpt4all.echo", - action="store_true", - default=False, - help="Whether to echo the prompt.", - ) - parser.add_argument( - "--gpt4all.repeat_last_n", - type=int, - default=64, - help="Last n tokens to penalize.", - ) - parser.add_argument( - "--gpt4all.repeat_penalty", - type=float, - default=1.3, - help="The penalty to apply to repeated tokens.", - ) - parser.add_argument( - "--gpt4all.n_batch", - type=int, - default=1, - help="Batch size for prompt processing.", - ) - parser.add_argument( - "--gpt4all.streaming", - action="store_true", - default=False, - help="Whether to stream the results or not.", - ) - - def __init__(self): - super(GPT4ALLMiner, self).__init__() - print(self.config) - self.model = GPT4All( - model=self.config.gpt4all.model, - n_ctx=self.config.gpt4all.n_ctx, - n_parts=self.config.gpt4all.n_parts, - seed=self.config.gpt4all.seed, - f16_kv=self.config.gpt4all.f16_kv, - logits_all=self.config.gpt4all.logits_all, - vocab_only=self.config.gpt4all.vocab_only, - use_mlock=self.config.gpt4all.use_mlock, - embedding=self.config.gpt4all.embedding, - n_threads=self.config.gpt4all.n_threads, - n_predict=self.config.gpt4all.n_predict, - temp=self.config.gpt4all.temp, - top_p=self.config.gpt4all.top_p, - top_k=self.config.gpt4all.top_k, - echo=self.config.gpt4all.echo, - stop=["user: ", "bot: ", "system: "], - repeat_last_n=self.config.gpt4all.repeat_last_n, - repeat_penalty=self.config.gpt4all.repeat_penalty, - n_batch=self.config.gpt4all.n_batch, - streaming=self.config.gpt4all.streaming, - ) - - def backward( - self, messages: List[Dict[str, str]], response: str, rewards: FloatTensor - ) -> str: - pass - - @staticmethod - def _process_history(history: List[dict]) -> str: - processed_history = "" - for message in history: - if message["role"] == "system": - processed_history += "system: " + message["content"] + "\n" - if message["role"] == "assistant": - processed_history += "assistant: " + message["content"] + "\n" - if message["role"] == "user": - processed_history += "user: " + message["content"] + "\n" - return processed_history - - def forward(self, messages: List[Dict[str, str]]) -> str: - bittensor.logging.info("messages", str(messages)) - history = self._process_history(messages) - bittensor.logging.info("history", str(history)) - resp = self.model(history) - bittensor.logging.info("response", str(resp)) - return resp - - -if __name__ == "__main__": - bittensor.utils.version_checking() - GPT4ALLMiner().run() diff --git a/neurons/text/prompting/miners/gpt4all/requirements.txt b/neurons/text/prompting/miners/gpt4all/requirements.txt deleted file mode 100644 index 8e3ed575ce..0000000000 --- a/neurons/text/prompting/miners/gpt4all/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -langchain -pyllamacpp diff --git a/neurons/text/prompting/miners/huggingface/__init__.py b/neurons/text/prompting/miners/huggingface/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/neurons/text/prompting/miners/huggingface/chat_glm/README.md b/neurons/text/prompting/miners/huggingface/chat_glm/README.md deleted file mode 100644 index 7d22d11885..0000000000 --- a/neurons/text/prompting/miners/huggingface/chat_glm/README.md +++ /dev/null @@ -1,104 +0,0 @@ -## ChatGLM Miner - THUDM/chatglm-6b Language Model Serving with BitTensor - This code is for running a language model powered by ChatGLM through the BitTensor framework. - - # Example Usage - ``` - python3 -m pip install -r neurons/text/prompting/miners/huggingface/chat_glm/requirements.txt - python3 neurons/text/prompting/miners/huggingface/chat_glm/neuron.py - ``` - - # Full Usage - ``` - usage: neuron.py [-h] [--chat_glm.device CHAT_GLM.DEVICE] [--chat_glm.max_new_tokens CHAT_GLM.MAX_NEW_TOKENS] [--chat_glm.temperature CHAT_GLM.TEMPERATURE] [--chat_glm.do_sample] - [--netuid NETUID] [--neuron.name NEURON.NAME] [--neuron.blocks_per_epoch NEURON.BLOCKS_PER_EPOCH] [--neuron.no_set_weights] - [--neuron.max_batch_size NEURON.MAX_BATCH_SIZE] [--neuron.max_sequence_len NEURON.MAX_SEQUENCE_LEN] [--neuron.blacklist.hotkeys [NEURON.BLACKLIST.HOTKEYS ...]] - [--wallet.name WALLET.NAME] [--wallet.hotkey WALLET.HOTKEY] [--wallet.path WALLET.PATH] [--wallet._mock] [--wallet.reregister WALLET.REREGISTER] - [--axon.priority.max_workers AXON.PRIORITY.MAX_WORKERS] [--axon.priority.maxsize AXON.PRIORITY.MAXSIZE] [--axon.port AXON.PORT] [--axon.ip AXON.IP] - [--axon.external_port AXON.EXTERNAL_PORT] [--axon.external_ip AXON.EXTERNAL_IP] [--axon.max_workers AXON.MAX_WORKERS] - [--axon.maximum_concurrent_rpcs AXON.MAXIMUM_CONCURRENT_RPCS] [--subtensor.network SUBTENSOR.NETWORK] [--subtensor.chain_endpoint SUBTENSOR.CHAIN_ENDPOINT] - [--subtensor._mock] [--subtensor.register.num_processes SUBTENSOR.REGISTER.NUM_PROCESSES] - [--subtensor.register.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL] [--subtensor.register.no_output_in_place] [--subtensor.register.verbose] - [--subtensor.register.cuda.use_cuda] [--subtensor.register.cuda.no_cuda] - [--subtensor.register.cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...]] - [--subtensor.register.cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB] [--logging.debug] [--logging.trace] [--logging.record_log] - [--logging.logging_dir LOGGING.LOGGING_DIR] [--metagraph._mock] [--config CONFIG] [--strict] - - optional arguments: - -h, --help show this help message and exit - --chat_glm.device CHAT_GLM.DEVICE - Device to load model - --chat_glm.max_new_tokens CHAT_GLM.MAX_NEW_TOKENS - Max tokens for model output. - --chat_glm.temperature CHAT_GLM.TEMPERATURE - Sampling temperature of model - --chat_glm.do_sample Whether to use sampling or not (if not, uses greedy decoding). - --netuid NETUID Subnet netuid - --neuron.name NEURON.NAME - Trials for this miner go in miner.root / (wallet_cold - wallet_hot) / miner.name - --neuron.blocks_per_epoch NEURON.BLOCKS_PER_EPOCH - Blocks until the miner sets weights on chain - --neuron.no_set_weights - If True, the model does not set weights. - --neuron.max_batch_size NEURON.MAX_BATCH_SIZE - The maximum batch size for forward requests. - --neuron.max_sequence_len NEURON.MAX_SEQUENCE_LEN - The maximum sequence length for forward requests. - --neuron.blacklist.hotkeys [NEURON.BLACKLIST.HOTKEYS ...] - To blacklist certain hotkeys - --wallet.name WALLET.NAME - The name of the wallet to unlock for running bittensor (name mock is reserved for mocking this wallet) - --wallet.hotkey WALLET.HOTKEY - The name of wallet's hotkey. - --wallet.path WALLET.PATH - The path to your bittensor wallets - --wallet._mock To turn on wallet mocking for testing purposes. - --wallet.reregister WALLET.REREGISTER - Whether to reregister the wallet if it is not already registered. - --axon.priority.max_workers AXON.PRIORITY.MAX_WORKERS - maximum number of threads in thread pool - --axon.priority.maxsize AXON.PRIORITY.MAXSIZE - maximum size of tasks in priority queue - --axon.port AXON.PORT - The local port this axon endpoint is bound to. i.e. 8091 - --axon.ip AXON.IP The local ip this axon binds to. ie. [::] - --axon.external_port AXON.EXTERNAL_PORT - The public port this axon broadcasts to the network. i.e. 8091 - --axon.external_ip AXON.EXTERNAL_IP - The external ip this axon broadcasts to the network to. ie. [::] - --axon.max_workers AXON.MAX_WORKERS - The maximum number connection handler threads working simultaneously on this endpoint. The grpc server distributes new worker threads to service requests - up to this number. - --axon.maximum_concurrent_rpcs AXON.MAXIMUM_CONCURRENT_RPCS - Maximum number of allowed active connections - --subtensor.network SUBTENSOR.NETWORK - The subtensor network flag. The likely choices are: -- finney (main network) -- local (local running network) -- mock (creates a mock connection (for - testing)) If this option is set it overloads subtensor.chain_endpoint with an entry point node from that network. - --subtensor.chain_endpoint SUBTENSOR.CHAIN_ENDPOINT - The subtensor endpoint flag. If set, overrides the --network flag. - --subtensor._mock To turn on subtensor mocking for testing purposes. - --subtensor.register.num_processes SUBTENSOR.REGISTER.NUM_PROCESSES, -n SUBTENSOR.REGISTER.NUM_PROCESSES - Number of processors to use for registration - --subtensor.register.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, --subtensor.register.cuda.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, --cuda.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, -u SUBTENSOR.REGISTER.UPDATE_INTERVAL - The number of nonces to process before checking for next block during registration - --subtensor.register.no_output_in_place, --no_output_in_place - Whether to not ouput the registration statistics in-place. Set flag to disable output in-place. - --subtensor.register.verbose - Whether to ouput the registration statistics verbosely. - --subtensor.register.cuda.use_cuda, --cuda, --cuda.use_cuda - Set flag to use CUDA to register. - --subtensor.register.cuda.no_cuda, --no_cuda, --cuda.no_cuda - Set flag to not use CUDA for registration - --subtensor.register.cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...], --cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...] - Set the CUDA device id(s). Goes by the order of speed. (i.e. 0 is the fastest). - --subtensor.register.cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB, --cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB - Set the number of Threads Per Block for CUDA. - --logging.debug Turn on bittensor debugging information - --logging.trace Turn on bittensor trace level information - --logging.record_log Turns on logging to file. - --logging.logging_dir LOGGING.LOGGING_DIR - Logging default root directory. - --metagraph._mock To turn on metagraph mocking for testing purposes. - --config CONFIG If set, defaults are overridden by passed file. - --strict If flagged, config will check that only exact arguemnts have been set. - ``` \ No newline at end of file diff --git a/neurons/text/prompting/miners/huggingface/chat_glm/neuron.py b/neurons/text/prompting/miners/huggingface/chat_glm/neuron.py deleted file mode 100644 index 3836197961..0000000000 --- a/neurons/text/prompting/miners/huggingface/chat_glm/neuron.py +++ /dev/null @@ -1,65 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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 argparse -import bittensor -from typing import List, Dict -from transformers import AutoTokenizer, AutoModel - - -class ChatGLMMiner(bittensor.HuggingFaceMiner): - arg_prefix: str = "chat_glm" - assistant_label: str = "" - user_label: str = "" - system_label: str = "" - - def load_tokenizer(self): - return AutoTokenizer.from_pretrained("THUDM/chatglm-6b", trust_remote_code=True) - - def load_model(self): - return AutoModel.from_pretrained( - "THUDM/chatglm-6b", trust_remote_code=True, torch_dtype=torch.float16 - ) - - def forward(self, messages: List[Dict[str, str]]) -> str: - history = self.process_history(messages) - prompt = history[-1][-1] - if len(history) == 1: - history = [] - generation, history = self.model.chat( - self.tokenizer, - prompt, - history, - max_length=self.config.chat_glm.max_new_tokens, - temperature=self.config.chat_glm.temperature, - do_sample=self.config.chat_glm.do_sample, - pad_token_id=self.tokenizer.eos_token_id, - ) - - bittensor.logging.debug( - "Message: " + str(messages).replace("<", "-").replace(">", "-") - ) - bittensor.logging.debug( - "Generation: " + str(generation).replace("<", "-").replace(">", "-") - ) - return generation - - -if __name__ == "__main__": - bittensor.utils.version_checking() - ChatGLMMiner().run() diff --git a/neurons/text/prompting/miners/huggingface/chat_glm/requirements.txt b/neurons/text/prompting/miners/huggingface/chat_glm/requirements.txt deleted file mode 100644 index bb07cd001d..0000000000 --- a/neurons/text/prompting/miners/huggingface/chat_glm/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -protobuf==3.20.0 -transformers==4.27.1 -icetk -cpm_kernels \ No newline at end of file diff --git a/neurons/text/prompting/miners/huggingface/dolly/README.md b/neurons/text/prompting/miners/huggingface/dolly/README.md deleted file mode 100644 index 724506535b..0000000000 --- a/neurons/text/prompting/miners/huggingface/dolly/README.md +++ /dev/null @@ -1,114 +0,0 @@ - -## Databricks Dolly 3B/12B Miner -Dolyl 3B and 12B completion miner for bittensor's prompting network. - -# Example Usage -``` -python3 neurons/text/prompting/miners/huggingface/dolly/neuron.py --dolly.model_name databricks/dolly-v2-12b -``` - -# Full Usage -``` -usage: neuron.py [-h] [--dolly.model_name DOLLY.MODEL_NAME] [--dolly.device DOLLY.DEVICE] [--dolly.max_new_tokens DOLLY.MAX_NEW_TOKENS] [--dolly.temperature DOLLY.TEMPERATURE] - [--dolly.do_sample] [--dolly.do_prompt_injection] [--dolly.system_prompt DOLLY.SYSTEM_PROMPT] [--netuid NETUID] [--neuron.name NEURON.NAME] - [--neuron.blocks_per_epoch NEURON.BLOCKS_PER_EPOCH] [--neuron.no_set_weights] [--neuron.max_batch_size NEURON.MAX_BATCH_SIZE] - [--neuron.max_sequence_len NEURON.MAX_SEQUENCE_LEN] [--neuron.blacklist.hotkeys [NEURON.BLACKLIST.HOTKEYS [NEURON.BLACKLIST.HOTKEYS ...]]] - [--neuron.blacklist.allow_non_registered] [--neuron.blacklist.default_stake NEURON.BLACKLIST.DEFAULT_STAKE] [--neuron.default_priority NEURON.DEFAULT_PRIORITY] - [--wallet.name WALLET.NAME] [--wallet.hotkey WALLET.HOTKEY] [--wallet.path WALLET.PATH] [--wallet._mock] [--wallet.reregister WALLET.REREGISTER] - [--axon.priority.max_workers AXON.PRIORITY.MAX_WORKERS] [--axon.priority.maxsize AXON.PRIORITY.MAXSIZE] [--axon.port AXON.PORT] [--axon.ip AXON.IP] - [--axon.external_port AXON.EXTERNAL_PORT] [--axon.external_ip AXON.EXTERNAL_IP] [--axon.max_workers AXON.MAX_WORKERS] - [--axon.maximum_concurrent_rpcs AXON.MAXIMUM_CONCURRENT_RPCS] [--subtensor.network SUBTENSOR.NETWORK] [--subtensor.chain_endpoint SUBTENSOR.CHAIN_ENDPOINT] - [--subtensor._mock] [--subtensor.register.num_processes SUBTENSOR.REGISTER.NUM_PROCESSES] [--subtensor.register.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL] - [--subtensor.register.no_output_in_place] [--subtensor.register.verbose] [--subtensor.register.cuda.use_cuda] [--subtensor.register.cuda.no_cuda] - [--subtensor.register.cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...]] [--subtensor.register.cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB] - [--logging.debug] [--logging.trace] [--logging.record_log] [--logging.logging_dir LOGGING.LOGGING_DIR] [--config CONFIG] [--strict] - -optional arguments: - -h, --help show this help message and exit - --dolly.model_name DOLLY.MODEL_NAME - Name/path of model to load - --dolly.device DOLLY.DEVICE - Device to load model - --dolly.max_new_tokens DOLLY.MAX_NEW_TOKENS - Max tokens for model output. - --dolly.temperature DOLLY.TEMPERATURE - Sampling temperature of model - --dolly.do_sample Whether to use sampling or not (if not, uses greedy decoding). - --dolly.do_prompt_injection - Whether to use a custom "system" prompt instead of the one sent by bittensor. - --dolly.system_prompt DOLLY.SYSTEM_PROMPT - What prompt to replace the system prompt with - --netuid NETUID Subnet netuid - --neuron.name NEURON.NAME - Trials for this miner go in miner.root / (wallet_cold - wallet_hot) / miner.name - --neuron.blocks_per_epoch NEURON.BLOCKS_PER_EPOCH - Blocks until the miner sets weights on chain - --neuron.no_set_weights - If True, the model does not set weights. - --neuron.max_batch_size NEURON.MAX_BATCH_SIZE - The maximum batch size for forward requests. - --neuron.max_sequence_len NEURON.MAX_SEQUENCE_LEN - The maximum sequence length for forward requests. - --neuron.blacklist.hotkeys [NEURON.BLACKLIST.HOTKEYS [NEURON.BLACKLIST.HOTKEYS ...]] - To blacklist certain hotkeys - --neuron.blacklist.allow_non_registered - If True, the miner will allow non-registered hotkeys to mine. - --neuron.blacklist.default_stake NEURON.BLACKLIST.DEFAULT_STAKE - Set default stake for miners. - --neuron.default_priority NEURON.DEFAULT_PRIORITY - Set default priority for miners. - --wallet.name WALLET.NAME - The name of the wallet to unlock for running bittensor (name mock is reserved for mocking this wallet) - --wallet.hotkey WALLET.HOTKEY - The name of wallet's hotkey. - --wallet.path WALLET.PATH - The path to your bittensor wallets - --wallet._mock To turn on wallet mocking for testing purposes. - --wallet.reregister WALLET.REREGISTER - Whether to reregister the wallet if it is not already registered. - --axon.priority.max_workers AXON.PRIORITY.MAX_WORKERS - maximum number of threads in thread pool - --axon.priority.maxsize AXON.PRIORITY.MAXSIZE - maximum size of tasks in priority queue - --axon.port AXON.PORT - The local port this axon endpoint is bound to. i.e. 8091 - --axon.ip AXON.IP The local ip this axon binds to. ie. [::] - --axon.external_port AXON.EXTERNAL_PORT - The public port this axon broadcasts to the network. i.e. 8091 - --axon.external_ip AXON.EXTERNAL_IP - The external ip this axon broadcasts to the network to. ie. [::] - --axon.max_workers AXON.MAX_WORKERS - The maximum number connection handler threads working simultaneously on this endpoint. The grpc server distributes new worker threads to service requests up to - this number. - --axon.maximum_concurrent_rpcs AXON.MAXIMUM_CONCURRENT_RPCS - Maximum number of allowed active connections - --subtensor.network SUBTENSOR.NETWORK - The subtensor network flag. The likely choices are: -- finney (main network) -- local (local running network) -- mock (creates a mock connection (for testing)) - If this option is set it overloads subtensor.chain_endpoint with an entry point node from that network. - --subtensor.chain_endpoint SUBTENSOR.CHAIN_ENDPOINT - The subtensor endpoint flag. If set, overrides the --network flag. - --subtensor._mock To turn on subtensor mocking for testing purposes. - --subtensor.register.num_processes SUBTENSOR.REGISTER.NUM_PROCESSES, -n SUBTENSOR.REGISTER.NUM_PROCESSES - Number of processors to use for registration - --subtensor.register.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, --subtensor.register.cuda.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, --cuda.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, -u SUBTENSOR.REGISTER.UPDATE_INTERVAL - The number of nonces to process before checking for next block during registration - --subtensor.register.no_output_in_place, --no_output_in_place - Whether to not ouput the registration statistics in-place. Set flag to disable output in-place. - --subtensor.register.verbose - Whether to ouput the registration statistics verbosely. - --subtensor.register.cuda.use_cuda, --cuda, --cuda.use_cuda - Set flag to use CUDA to register. - --subtensor.register.cuda.no_cuda, --no_cuda, --cuda.no_cuda - Set flag to not use CUDA for registration - --subtensor.register.cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...], --cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...] - Set the CUDA device id(s). Goes by the order of speed. (i.e. 0 is the fastest). - --subtensor.register.cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB, --cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB - Set the number of Threads Per Block for CUDA. - --logging.debug Turn on bittensor debugging information - --logging.trace Turn on bittensor trace level information - --logging.record_log Turns on logging to file. - --logging.logging_dir LOGGING.LOGGING_DIR - Logging default root directory. - --config CONFIG If set, defaults are overridden by passed file. - --strict If flagged, config will check that only exact arguemnts have been set. - ``` diff --git a/neurons/text/prompting/miners/huggingface/dolly/neuron.py b/neurons/text/prompting/miners/huggingface/dolly/neuron.py deleted file mode 100644 index 5a1d98b433..0000000000 --- a/neurons/text/prompting/miners/huggingface/dolly/neuron.py +++ /dev/null @@ -1,56 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2023 Yuma Rao - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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 -from typing import List, Dict -from transformers import pipeline - - -class Dolly12BMiner(bittensor.HuggingFaceMiner): - arg_prefix: str = "dolly" - assistant_label: str = "### Response:" - user_label: str = "### Instruction:" - system_label: str = "" - - def load_model(self): - bittensor.logging.info("Loading " + str(self.config.dolly.model_name)) - model = pipeline( - model=self.config.dolly.model_name, - torch_dtype=torch.bfloat16, - trust_remote_code=True, - device=0, - ) - bittensor.logging.info("Model loaded!") - return model - - def load_tokenizer(self): - pass - - def forward(self, messages: List[Dict[str, str]]) -> str: - history = self.process_history(messages) - prompt = history + self.assistant_label - generation = self.model(prompt) - - bittensor.logging.debug(" Message: " + str(messages)) - bittensor.logging.debug("Generation: " + str(generation)) - return generation - - -if __name__ == "__main__": - bittensor.utils.version_checking() - Dolly12BMiner().run() diff --git a/neurons/text/prompting/miners/huggingface/dolly/requirements.txt b/neurons/text/prompting/miners/huggingface/dolly/requirements.txt deleted file mode 100644 index fe2343560f..0000000000 --- a/neurons/text/prompting/miners/huggingface/dolly/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -accelerate>=0.16.0,<1 -transformers[torch]>=4.28.1,<5 -torch>=1.13.1,<2" \ No newline at end of file diff --git a/neurons/text/prompting/miners/huggingface/dromedary/README.md b/neurons/text/prompting/miners/huggingface/dromedary/README.md deleted file mode 100644 index c526dac858..0000000000 --- a/neurons/text/prompting/miners/huggingface/dromedary/README.md +++ /dev/null @@ -1,116 +0,0 @@ - -## Dromedary Miner -Dromedary 65B completion miner for bittensor's prompting network. - -# Example Usage -``` -python3 neurons/text/prompting/miners/huggingface/dromedary/neuron.py -``` - -# Full Usage -``` -usage: neuron.py [-h] [--dromedary.model_name DROMEDARY.MODEL_NAME] [--dromedary.device DROMEDARY.DEVICE] [--dromedary.max_new_tokens DROMEDARY.MAX_NEW_TOKENS] - [--dromedary.temperature DROMEDARY.TEMPERATURE] [--dromedary.do_sample] [--dromedary.do_prompt_injection] [--dromedary.system_prompt DROMEDARY.SYSTEM_PROMPT] - [--netuid NETUID] [--neuron.name NEURON.NAME] [--neuron.blocks_per_epoch NEURON.BLOCKS_PER_EPOCH] [--neuron.no_set_weights] - [--neuron.max_batch_size NEURON.MAX_BATCH_SIZE] [--neuron.max_sequence_len NEURON.MAX_SEQUENCE_LEN] - [--neuron.blacklist.hotkeys [NEURON.BLACKLIST.HOTKEYS [NEURON.BLACKLIST.HOTKEYS ...]]] [--neuron.blacklist.allow_non_registered] - [--neuron.blacklist.default_stake NEURON.BLACKLIST.DEFAULT_STAKE] [--neuron.default_priority NEURON.DEFAULT_PRIORITY] [--wallet.name WALLET.NAME] - [--wallet.hotkey WALLET.HOTKEY] [--wallet.path WALLET.PATH] [--wallet._mock] [--wallet.reregister WALLET.REREGISTER] - [--axon.priority.max_workers AXON.PRIORITY.MAX_WORKERS] [--axon.priority.maxsize AXON.PRIORITY.MAXSIZE] [--axon.port AXON.PORT] [--axon.ip AXON.IP] - [--axon.external_port AXON.EXTERNAL_PORT] [--axon.external_ip AXON.EXTERNAL_IP] [--axon.max_workers AXON.MAX_WORKERS] - [--axon.maximum_concurrent_rpcs AXON.MAXIMUM_CONCURRENT_RPCS] [--subtensor.network SUBTENSOR.NETWORK] [--subtensor.chain_endpoint SUBTENSOR.CHAIN_ENDPOINT] - [--subtensor._mock] [--subtensor.register.num_processes SUBTENSOR.REGISTER.NUM_PROCESSES] [--subtensor.register.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL] - [--subtensor.register.no_output_in_place] [--subtensor.register.verbose] [--subtensor.register.cuda.use_cuda] [--subtensor.register.cuda.no_cuda] - [--subtensor.register.cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...]] [--subtensor.register.cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB] - [--logging.debug] [--logging.trace] [--logging.record_log] [--logging.logging_dir LOGGING.LOGGING_DIR] [--config CONFIG] [--strict] - -optional arguments: - -h, --help show this help message and exit - --dromedary.model_name DROMEDARY.MODEL_NAME - Name/path of model to load - --dromedary.device DROMEDARY.DEVICE - Device to load model - --dromedary.max_new_tokens DROMEDARY.MAX_NEW_TOKENS - Max tokens for model output. - --dromedary.temperature DROMEDARY.TEMPERATURE - Sampling temperature of model - --dromedary.do_sample - Whether to use sampling or not (if not, uses greedy decoding). - --dromedary.do_prompt_injection - Whether to use a custom "system" prompt instead of the one sent by bittensor. - --dromedary.system_prompt DROMEDARY.SYSTEM_PROMPT - What prompt to replace the system prompt with - --netuid NETUID Subnet netuid - --neuron.name NEURON.NAME - Trials for this miner go in miner.root / (wallet_cold - wallet_hot) / miner.name - --neuron.blocks_per_epoch NEURON.BLOCKS_PER_EPOCH - Blocks until the miner sets weights on chain - --neuron.no_set_weights - If True, the model does not set weights. - --neuron.max_batch_size NEURON.MAX_BATCH_SIZE - The maximum batch size for forward requests. - --neuron.max_sequence_len NEURON.MAX_SEQUENCE_LEN - The maximum sequence length for forward requests. - --neuron.blacklist.hotkeys [NEURON.BLACKLIST.HOTKEYS [NEURON.BLACKLIST.HOTKEYS ...]] - To blacklist certain hotkeys - --neuron.blacklist.allow_non_registered - If True, the miner will allow non-registered hotkeys to mine. - --neuron.blacklist.default_stake NEURON.BLACKLIST.DEFAULT_STAKE - Set default stake for miners. - --neuron.default_priority NEURON.DEFAULT_PRIORITY - Set default priority for miners. - --wallet.name WALLET.NAME - The name of the wallet to unlock for running bittensor (name mock is reserved for mocking this wallet) - --wallet.hotkey WALLET.HOTKEY - The name of wallet's hotkey. - --wallet.path WALLET.PATH - The path to your bittensor wallets - --wallet._mock To turn on wallet mocking for testing purposes. - --wallet.reregister WALLET.REREGISTER - Whether to reregister the wallet if it is not already registered. - --axon.priority.max_workers AXON.PRIORITY.MAX_WORKERS - maximum number of threads in thread pool - --axon.priority.maxsize AXON.PRIORITY.MAXSIZE - maximum size of tasks in priority queue - --axon.port AXON.PORT - The local port this axon endpoint is bound to. i.e. 8091 - --axon.ip AXON.IP The local ip this axon binds to. ie. [::] - --axon.external_port AXON.EXTERNAL_PORT - The public port this axon broadcasts to the network. i.e. 8091 - --axon.external_ip AXON.EXTERNAL_IP - The external ip this axon broadcasts to the network to. ie. [::] - --axon.max_workers AXON.MAX_WORKERS - The maximum number connection handler threads working simultaneously on this endpoint. The grpc server distributes new worker threads to service requests up - to this number. - --axon.maximum_concurrent_rpcs AXON.MAXIMUM_CONCURRENT_RPCS - Maximum number of allowed active connections - --subtensor.network SUBTENSOR.NETWORK - The subtensor network flag. The likely choices are: -- finney (main network) -- local (local running network) -- mock (creates a mock connection (for - testing)) If this option is set it overloads subtensor.chain_endpoint with an entry point node from that network. - --subtensor.chain_endpoint SUBTENSOR.CHAIN_ENDPOINT - The subtensor endpoint flag. If set, overrides the --network flag. - --subtensor._mock To turn on subtensor mocking for testing purposes. - --subtensor.register.num_processes SUBTENSOR.REGISTER.NUM_PROCESSES, -n SUBTENSOR.REGISTER.NUM_PROCESSES - Number of processors to use for registration - --subtensor.register.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, --subtensor.register.cuda.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, --cuda.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, -u SUBTENSOR.REGISTER.UPDATE_INTERVAL - The number of nonces to process before checking for next block during registration - --subtensor.register.no_output_in_place, --no_output_in_place - Whether to not ouput the registration statistics in-place. Set flag to disable output in-place. - --subtensor.register.verbose - Whether to ouput the registration statistics verbosely. - --subtensor.register.cuda.use_cuda, --cuda, --cuda.use_cuda - Set flag to use CUDA to register. - --subtensor.register.cuda.no_cuda, --no_cuda, --cuda.no_cuda - Set flag to not use CUDA for registration - --subtensor.register.cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...], --cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...] - Set the CUDA device id(s). Goes by the order of speed. (i.e. 0 is the fastest). - --subtensor.register.cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB, --cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB - Set the number of Threads Per Block for CUDA. - --logging.debug Turn on bittensor debugging information - --logging.trace Turn on bittensor trace level information - --logging.record_log Turns on logging to file. - --logging.logging_dir LOGGING.LOGGING_DIR - Logging default root directory. - --config CONFIG If set, defaults are overridden by passed file. - --strict If flagged, config will check that only exact arguemnts have been set. -``` \ No newline at end of file diff --git a/neurons/text/prompting/miners/huggingface/dromedary/neuron.py b/neurons/text/prompting/miners/huggingface/dromedary/neuron.py deleted file mode 100644 index a62a8ca53d..0000000000 --- a/neurons/text/prompting/miners/huggingface/dromedary/neuron.py +++ /dev/null @@ -1,81 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2023 Yuma Rao - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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 argparse -import bittensor -from typing import List, Dict -from transformers import AutoTokenizer, AutoModelForCausalLM - - -class DromedaryMiner(bittensor.HuggingFaceMiner): - arg_prefix: str = "dromedary" - assistant_label: str = "Dromedary:" - user_label: str = "User:" - system_label: str = "System:" - - @classmethod - def add_args(cls, parser: argparse.ArgumentParser): - parser.add_argument( - "--dromedary.device_map", - type=str, - help='Device to load model: Default "auto" for multi-GPU', - default="auto", - ) - - def __init__(self): - super(DromedaryMiner, self).__init__() - print(self.config) - - bittensor.logging.info("Loading " + str(self.config.dromedary.model_name)) - self.tokenizer = AutoTokenizer.from_pretrained( - self.config.dromedary.model_name, use_fast=False - ) - self.model = AutoModelForCausalLM.from_pretrained( - self.config.dromedary.model_name, - device_map=self.config.dromedary.device_map, - torch_dtype=torch.float16, - low_cpu_mem_usage=True, - ) - bittensor.logging.info("Model loaded!") - - def forward(self, messages: List[Dict[str, str]]) -> str: - history = self._process_history(self, messages) - prompt = history + self.assistant_label - - input_ids = self.tokenizer.encode(prompt, return_tensors="pt").to( - self.config.dromedary.device - ) - output = self.model.generate( - input_ids, - max_length=input_ids.shape[1] + self.config.dromedary.max_new_tokens, - temperature=self.config.dromedary.temperature, - do_sample=self.config.dromedary.do_sample, - pad_token_id=self.tokenizer.eos_token_id, - ) - generation = self.tokenizer.decode( - output[0][input_ids.shape[1] :], skip_special_tokens=True - ) - - bittensor.logging.debug("Message: " + str(messages)) - bittensor.logging.debug("Generation: " + str(generation)) - return generation - - -if __name__ == "__main__": - bittensor.utils.version_checking() - DromedaryMiner().run() diff --git a/neurons/text/prompting/miners/huggingface/fastchat_t5/README.md b/neurons/text/prompting/miners/huggingface/fastchat_t5/README.md deleted file mode 100644 index d74449977b..0000000000 --- a/neurons/text/prompting/miners/huggingface/fastchat_t5/README.md +++ /dev/null @@ -1,127 +0,0 @@ -# FastChat T5 Miner -FastChat T5 completion miner for bittensor's prompting network. - -# Download weights -They disabled API inference requests via HuggingFace so you've gotta do it yourself by downloading the weights and passing the path directly. - -```bash -git lfs install -git clone https://huggingface.co/lmsys/fastchat-t5-3b-v1.0 -``` - -# Example Usage -``` -python3 neurons/text/prompting/miners/huggingface/fastchat_t5/neuron.py --fastchat_t5.model_path /path/to/fastchat-t5-3b-v1.0 -``` - -# Full Usage -``` -usage: fastchat-t5.py [-h] [--fastchat_t5.MODEL_PATH FASTCHAT_T5.MODEL_PATH] [--fastchat_t5.device FASTCHAT_T5.DEVICE] [--fastchat_t5.max_new_tokens FASTCHAT_T5.MAX_NEW_TOKENS] - [--fastchat_t5.temperature FASTCHAT_T5.TEMPERATURE] [--fastchat_t5.greedy_decoding] [--fastchat_t5.repetition_penalty FASTCHAT_T5.REPETITION_PENALTY] - [--fastchat_t5.do_prompt_injection] [--fastchat_t5.system_prompt FASTCHAT_T5.SYSTEM_PROMPT] [--netuid NETUID] [--neuron.name NEURON.NAME] - [--neuron.blocks_per_epoch NEURON.BLOCKS_PER_EPOCH] [--neuron.no_set_weights] [--neuron.max_batch_size NEURON.MAX_BATCH_SIZE] - [--neuron.max_sequence_len NEURON.MAX_SEQUENCE_LEN] [--neuron.blacklist.hotkeys [NEURON.BLACKLIST.HOTKEYS [NEURON.BLACKLIST.HOTKEYS ...]]] - [--neuron.blacklist.allow_non_registered] [--neuron.blacklist.default_stake NEURON.BLACKLIST.DEFAULT_STAKE] - [--neuron.default_priority NEURON.DEFAULT_PRIORITY] [--wallet.name WALLET.NAME] [--wallet.hotkey WALLET.HOTKEY] [--wallet.path WALLET.PATH] [--wallet._mock] - [--wallet.reregister WALLET.REREGISTER] [--axon.priority.max_workers AXON.PRIORITY.MAX_WORKERS] [--axon.priority.maxsize AXON.PRIORITY.MAXSIZE] - [--axon.port AXON.PORT] [--axon.ip AXON.IP] [--axon.external_port AXON.EXTERNAL_PORT] [--axon.external_ip AXON.EXTERNAL_IP] - [--axon.max_workers AXON.MAX_WORKERS] [--axon.maximum_concurrent_rpcs AXON.MAXIMUM_CONCURRENT_RPCS] [--subtensor.network SUBTENSOR.NETWORK] - [--subtensor.chain_endpoint SUBTENSOR.CHAIN_ENDPOINT] [--subtensor._mock] [--subtensor.register.num_processes SUBTENSOR.REGISTER.NUM_PROCESSES] - [--subtensor.register.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL] [--subtensor.register.no_output_in_place] [--subtensor.register.verbose] - [--subtensor.register.cuda.use_cuda] [--subtensor.register.cuda.no_cuda] - [--subtensor.register.cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...]] - [--subtensor.register.cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB] [--logging.debug] [--logging.trace] [--logging.record_log] - [--logging.logging_dir LOGGING.LOGGING_DIR] [--config CONFIG] [--strict] - -optional arguments: - -h, --help show this help message and exit - --fastchat_t5.MODEL_PATH FASTCHAT_T5.MODEL_PATH - Name/path of model to load - --fastchat_t5.device FASTCHAT_T5.DEVICE - Device to load model - --fastchat_t5.max_new_tokens FASTCHAT_T5.MAX_NEW_TOKENS - Max tokens for model output. - --fastchat_t5.temperature FASTCHAT_T5.TEMPERATURE - Sampling temperature of model - --fastchat_t5.greedy_decoding - Whether to use greedy sampling or not (if not, uses multinomial sampling). - --fastchat_t5.repetition_penalty FASTCHAT_T5.REPETITION_PENALTY - Repetition penalty for model - --fastchat_t5.do_prompt_injection - Whether to use a custom "system" prompt instead of the one sent by bittensor. - --fastchat_t5.system_prompt FASTCHAT_T5.SYSTEM_PROMPT - What prompt to replace the system prompt with - --netuid NETUID Subnet netuid - --neuron.name NEURON.NAME - Trials for this miner go in miner.root / (wallet_cold - wallet_hot) / miner.name - --neuron.blocks_per_epoch NEURON.BLOCKS_PER_EPOCH - Blocks until the miner sets weights on chain - --neuron.no_set_weights - If True, the model does not set weights. - --neuron.max_batch_size NEURON.MAX_BATCH_SIZE - The maximum batch size for forward requests. - --neuron.max_sequence_len NEURON.MAX_SEQUENCE_LEN - The maximum sequence length for forward requests. - --neuron.blacklist.hotkeys [NEURON.BLACKLIST.HOTKEYS [NEURON.BLACKLIST.HOTKEYS ...]] - To blacklist certain hotkeys - --neuron.blacklist.allow_non_registered - If True, the miner will allow non-registered hotkeys to mine. - --neuron.blacklist.default_stake NEURON.BLACKLIST.DEFAULT_STAKE - Set default stake for miners. - --neuron.default_priority NEURON.DEFAULT_PRIORITY - Set default priority for miners. - --wallet.name WALLET.NAME - The name of the wallet to unlock for running bittensor (name mock is reserved for mocking this wallet) - --wallet.hotkey WALLET.HOTKEY - The name of wallet's hotkey. - --wallet.path WALLET.PATH - The path to your bittensor wallets - --wallet._mock To turn on wallet mocking for testing purposes. - --wallet.reregister WALLET.REREGISTER - Whether to reregister the wallet if it is not already registered. - --axon.priority.max_workers AXON.PRIORITY.MAX_WORKERS - maximum number of threads in thread pool - --axon.priority.maxsize AXON.PRIORITY.MAXSIZE - maximum size of tasks in priority queue - --axon.port AXON.PORT - The local port this axon endpoint is bound to. i.e. 8091 - --axon.ip AXON.IP The local ip this axon binds to. ie. [::] - --axon.external_port AXON.EXTERNAL_PORT - The public port this axon broadcasts to the network. i.e. 8091 - --axon.external_ip AXON.EXTERNAL_IP - The external ip this axon broadcasts to the network to. ie. [::] - --axon.max_workers AXON.MAX_WORKERS - The maximum number connection handler threads working simultaneously on this endpoint. The grpc server distributes new worker threads to service requests up - to this number. - --axon.maximum_concurrent_rpcs AXON.MAXIMUM_CONCURRENT_RPCS - Maximum number of allowed active connections - --subtensor.network SUBTENSOR.NETWORK - The subtensor network flag. The likely choices are: -- finney (main network) -- local (local running network) -- mock (creates a mock connection (for - testing)) If this option is set it overloads subtensor.chain_endpoint with an entry point node from that network. - --subtensor.chain_endpoint SUBTENSOR.CHAIN_ENDPOINT - The subtensor endpoint flag. If set, overrides the --network flag. - --subtensor._mock To turn on subtensor mocking for testing purposes. - --subtensor.register.num_processes SUBTENSOR.REGISTER.NUM_PROCESSES, -n SUBTENSOR.REGISTER.NUM_PROCESSES - Number of processors to use for registration - --subtensor.register.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, --subtensor.register.cuda.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, --cuda.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, -u SUBTENSOR.REGISTER.UPDATE_INTERVAL - The number of nonces to process before checking for next block during registration - --subtensor.register.no_output_in_place, --no_output_in_place - Whether to not ouput the registration statistics in-place. Set flag to disable output in-place. - --subtensor.register.verbose - Whether to ouput the registration statistics verbosely. - --subtensor.register.cuda.use_cuda, --cuda, --cuda.use_cuda - Set flag to use CUDA to register. - --subtensor.register.cuda.no_cuda, --no_cuda, --cuda.no_cuda - Set flag to not use CUDA for registration - --subtensor.register.cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...], --cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...] - Set the CUDA device id(s). Goes by the order of speed. (i.e. 0 is the fastest). - --subtensor.register.cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB, --cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB - Set the number of Threads Per Block for CUDA. - --logging.debug Turn on bittensor debugging information - --logging.trace Turn on bittensor trace level information - --logging.record_log Turns on logging to file. - --logging.logging_dir LOGGING.LOGGING_DIR - Logging default root directory. - --config CONFIG If set, defaults are overridden by passed file. - --strict If flagged, config will check that only exact arguemnts have been set. -``` \ No newline at end of file diff --git a/neurons/text/prompting/miners/huggingface/fastchat_t5/neuron.py b/neurons/text/prompting/miners/huggingface/fastchat_t5/neuron.py deleted file mode 100644 index 5376bc15d1..0000000000 --- a/neurons/text/prompting/miners/huggingface/fastchat_t5/neuron.py +++ /dev/null @@ -1,51 +0,0 @@ -import torch -import argparse -import bittensor -from typing import List, Dict -from transformers import T5Tokenizer, AutoModelForSeq2SeqLM - - -class FastChatT5Miner(bittensor.HuggingFaceMiner): - arg_prefix: str = "fastchat_t5" - assistant_label: str = "ASSISTANT:" - user_label: str = "USER:" - system_label: str = "SYSTEM:" - - def load_model(self): - bittensor.logging.info("Loading " + str(self.config.fastchat_t5.model_name)) - model = AutoModelForSeq2SeqLM.from_pretrained( - self.config.fastchat_t5.model_name, - local_files_only=True, - low_cpu_mem_usage=True, - torch_dtype=torch.float16, - ) - bittensor.logging.info("Model loaded!") - return model - - def load_tokenizer(self): - return T5Tokenizer.from_pretrained( - self.config.fastchat_t5.model_name, local_files_only=True - ) - - def forward(self, messages: List[Dict[str, str]]) -> str: - history = self.process_history(messages) - prompt = history + self.assistant_label - input_ids = self.tokenizer.encode(prompt, return_tensors="pt").to( - self.config.fastchat_t5.device - ) - output = self.model.generate( - input_ids, - max_length=input_ids.shape[1] + self.config.fastchat_t5.max_new_tokens, - temperature=self.config.fastchat_t5.temperature, - pad_token_id=self.tokenizer.eos_token_id, - ) - generation = self.tokenizer.decode(output[0], skip_special_tokens=True) - - bittensor.logging.debug("Message: " + str(messages)) - bittensor.logging.debug("Generation: " + str(generation)) - return generation - - -if __name__ == "__main__": - bittensor.utils.version_checking() - FastChatT5Miner().run() diff --git a/neurons/text/prompting/miners/huggingface/gpt4_x_vicuna/README.md b/neurons/text/prompting/miners/huggingface/gpt4_x_vicuna/README.md deleted file mode 100644 index 733d99467a..0000000000 --- a/neurons/text/prompting/miners/huggingface/gpt4_x_vicuna/README.md +++ /dev/null @@ -1,139 +0,0 @@ -## Gpt4_x_vicuna Miner -Gpt4_x_vicuna Language Model Serving with BitTensor -This code is for running the Gpt4_x_vicuna by Nous Research model through the BitTensor framework. - -# Overview - -## Contents - -- [Licence](#Licence) -- [Installing Dependencies](#installing-dependencies) -- [Starting Miner](#starting-miner) - - -# Licence -gpl - -# Installing Dependencies - -``` -python3 -m pip install -r neurons/text/prompting/miners/huggingface/gpt4_x_vicuna/requirements.txt -``` - -# Starting Miner -To start the miner, all you need to do is to specify the path to the model, or the name on Huggingface hub, and it will be downloaded. - -You can find different model checkpoints by searching Huggingface, or by looking at Nous Research's Huggingface page https://huggingface.co/NousResearch - -``` -python3 neurons/text/prompting/miners/huggingface/gpt4_x_vicuna/neuron.py --gpt4_x_vicuna.model_name GPT4_X_VICUNA.MODEL_NAME_OR_PATH -``` - -# Full Usage -``` -usage: neuron.py [-h] [--gpt4_x_vicuna.model_name GPT4_X_VICUNA.MODEL_NAME] [--gpt4_x_vicuna.device GPT4_X_VICUNA.DEVICE] [--gpt4_x_vicuna.max_new_tokens GPT4_X_VICUNA.MAX_NEW_TOKENS] - [--gpt4_x_vicuna.temperature GPT4_X_VICUNA.TEMPERATURE] [--gpt4_x_vicuna.do_sample] [--netuid NETUID] [--neuron.name NEURON.NAME] - [--neuron.blocks_per_epoch NEURON.BLOCKS_PER_EPOCH] [--neuron.no_set_weights] - [--neuron.max_batch_size NEURON.MAX_BATCH_SIZE] [--neuron.max_sequence_len NEURON.MAX_SEQUENCE_LEN] - [--neuron.blacklist.hotkeys [NEURON.BLACKLIST.HOTKEYS ...]] [--neuron.blacklist.allow_non_registered] - [--neuron.blacklist.default_stake NEURON.BLACKLIST.DEFAULT_STAKE] [--neuron.default_priority NEURON.DEFAULT_PRIORITY] - [--wallet.name WALLET.NAME] [--wallet.hotkey WALLET.HOTKEY] [--wallet.path WALLET.PATH] [--wallet._mock] - [--wallet.reregister WALLET.REREGISTER] [--axon.priority.max_workers AXON.PRIORITY.MAX_WORKERS] - [--axon.priority.maxsize AXON.PRIORITY.MAXSIZE] [--axon.port AXON.PORT] [--axon.ip AXON.IP] - [--axon.external_port AXON.EXTERNAL_PORT] [--axon.external_ip AXON.EXTERNAL_IP] [--axon.max_workers AXON.MAX_WORKERS] - [--axon.maximum_concurrent_rpcs AXON.MAXIMUM_CONCURRENT_RPCS] [--subtensor.network SUBTENSOR.NETWORK] - [--subtensor.chain_endpoint SUBTENSOR.CHAIN_ENDPOINT] [--subtensor._mock] - [--subtensor.register.num_processes SUBTENSOR.REGISTER.NUM_PROCESSES] - [--subtensor.register.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL] [--subtensor.register.no_output_in_place] - [--subtensor.register.verbose] [--subtensor.register.cuda.use_cuda] [--subtensor.register.cuda.no_cuda] - [--subtensor.register.cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...]] - [--subtensor.register.cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB] [--logging.debug] [--logging.trace] [--logging.record_log] - [--logging.logging_dir LOGGING.LOGGING_DIR] [--metagraph._mock] [--config CONFIG] [--strict] - -optional arguments: - -h, --help show this help message and exit - --neoxt.model_name NEOXT.MODEL_NAME - Name/path of model to load of model to load - --gpt4_x_vicuna.device GPT4_X_VICUNA.DEVICE - Device to load model - --gpt4_x_vicuna.max_new_tokens GPT4_X_VICUNA.MAX_NEW_TOKENS - Max tokens for model output. - --gpt4_x_vicuna.temperature GPT4_X_VICUNA.TEMPERATURE - Sampling temperature of model - --gpt4_x_vicuna.do_sample Whether to use sampling or not (if not, uses greedy decoding). - --netuid NETUID Subnet netuid - --neuron.name NEURON.NAME - Trials for this miner go in miner.root / (wallet_cold - wallet_hot) / miner.name - --neuron.blocks_per_epoch NEURON.BLOCKS_PER_EPOCH - Blocks until the miner sets weights on chain - --neuron.no_set_weights - If True, the model does not set weights. - --neuron.max_batch_size NEURON.MAX_BATCH_SIZE - The maximum batch size for forward requests. - --neuron.max_sequence_len NEURON.MAX_SEQUENCE_LEN - The maximum sequence length for forward requests. - --neuron.blacklist.hotkeys [NEURON.BLACKLIST.HOTKEYS ...] - To blacklist certain hotkeys - --neuron.blacklist.allow_non_registered - If True, the miner will allow non-registered hotkeys to mine. - --neuron.blacklist.default_stake NEURON.BLACKLIST.DEFAULT_STAKE - Set default stake for miners. - --neuron.default_priority NEURON.DEFAULT_PRIORITY - Set default priority for miners. - --wallet.name WALLET.NAME - The name of the wallet to unlock for running bittensor (name mock is reserved for mocking this wallet) - --wallet.hotkey WALLET.HOTKEY - The name of wallet's hotkey. - --wallet.path WALLET.PATH - The path to your bittensor wallets - --wallet._mock To turn on wallet mocking for testing purposes. - --wallet.reregister WALLET.REREGISTER - Whether to reregister the wallet if it is not already registered. - --axon.priority.max_workers AXON.PRIORITY.MAX_WORKERS - maximum number of threads in thread pool - --axon.priority.maxsize AXON.PRIORITY.MAXSIZE - maximum size of tasks in priority queue - --axon.port AXON.PORT - The local port this axon endpoint is bound to. i.e. 8091 - --axon.ip AXON.IP The local ip this axon binds to. ie. [::] - --axon.external_port AXON.EXTERNAL_PORT - The public port this axon broadcasts to the network. i.e. 8091 - --axon.external_ip AXON.EXTERNAL_IP - The external ip this axon broadcasts to the network to. ie. [::] - --axon.max_workers AXON.MAX_WORKERS - The maximum number connection handler threads working simultaneously on this endpoint. The grpc server distributes - new worker threads to service requests up to this number. - --axon.maximum_concurrent_rpcs AXON.MAXIMUM_CONCURRENT_RPCS - Maximum number of allowed active connections - --subtensor.network SUBTENSOR.NETWORK - The subtensor network flag. The likely choices are: -- finney (main network) -- local (local running network) -- - mock (creates a mock connection (for testing)) If this option is set it overloads subtensor.chain_endpoint with an - entry point node from that network. - --subtensor.chain_endpoint SUBTENSOR.CHAIN_ENDPOINT - The subtensor endpoint flag. If set, overrides the --network flag. - --subtensor._mock To turn on subtensor mocking for testing purposes. - --subtensor.register.num_processes SUBTENSOR.REGISTER.NUM_PROCESSES, -n SUBTENSOR.REGISTER.NUM_PROCESSES - Number of processors to use for registration - --subtensor.register.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, --subtensor.register.cuda.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, --cuda.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, -u SUBTENSOR.REGISTER.UPDATE_INTERVAL - The number of nonces to process before checking for next block during registration - --subtensor.register.no_output_in_place, --no_output_in_place - Whether to not ouput the registration statistics in-place. Set flag to disable output in-place. - --subtensor.register.verbose - Whether to ouput the registration statistics verbosely. - --subtensor.register.cuda.use_cuda, --cuda, --cuda.use_cuda - Set flag to use CUDA to register. - --subtensor.register.cuda.no_cuda, --no_cuda, --cuda.no_cuda - Set flag to not use CUDA for registration - --subtensor.register.cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...], --cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...] - Set the CUDA device id(s). Goes by the order of speed. (i.e. 0 is the fastest). - --subtensor.register.cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB, --cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB - Set the number of Threads Per Block for CUDA. - --logging.debug Turn on bittensor debugging information - --logging.trace Turn on bittensor trace level information - --logging.record_log Turns on logging to file. - --logging.logging_dir LOGGING.LOGGING_DIR - Logging default root directory. - --metagraph._mock To turn on metagraph mocking for testing purposes. - --config CONFIG If set, defaults are overridden by passed file. - --strict If flagged, config will check that only exact arguemnts have been set. -``` \ No newline at end of file diff --git a/neurons/text/prompting/miners/huggingface/gpt4_x_vicuna/neuron.py b/neurons/text/prompting/miners/huggingface/gpt4_x_vicuna/neuron.py deleted file mode 100644 index b3421cae34..0000000000 --- a/neurons/text/prompting/miners/huggingface/gpt4_x_vicuna/neuron.py +++ /dev/null @@ -1,69 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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 -from typing import List, Dict -from transformers import AutoTokenizer, AutoModelForCausalLM - - -class Gpt4_x_vicunaMiner(bittensor.HuggingFaceMiner): - arg_prefix = "gpt4_x_vicuna" - system_label = "### System:" - user_label = "### User:" - assistant_label = "### Response:" - - def load_tokenizer(self): - return AutoTokenizer.from_pretrained( - self.config.gpt4_x_vicuna.model_name, use_fast=False - ) - - def load_model(self): - return AutoModelForCausalLM.from_pretrained( - self.config.gpt4_x_vicuna.model_name, - torch_dtype=torch.float16, - low_cpu_mem_usage=True, - ) - - def forward(self, messages: List[Dict[str, str]]) -> str: - history = self.process_history(messages) - prompt = history + self.assistant_label - - input_ids = self.tokenizer.encode(prompt, return_tensors="pt").to( - self.config.gpt4_x_vicuna.device - ) - - output = self.model.generate( - input_ids, - max_length=input_ids.shape[1] + self.config.gpt4_x_vicuna.max_new_tokens, - temperature=self.config.gpt4_x_vicuna.temperature, - do_sample=self.config.gpt4_x_vicuna.do_sample, - pad_token_id=self.tokenizer.eos_token_id, - ) - generation = self.tokenizer.decode( - output[0][input_ids.shape[1] :], skip_special_tokens=True - ) - - bittensor.logging.debug("Messages: " + str(messages)) - bittensor.logging.debug("Prompt: " + str(prompt)) - bittensor.logging.debug("Generation: " + str(generation)) - return generation - - -if __name__ == "__main__": - bittensor.utils.version_checking() - Gpt4_x_vicunaMiner().run() diff --git a/neurons/text/prompting/miners/huggingface/gpt4_x_vicuna/requirements.txt b/neurons/text/prompting/miners/huggingface/gpt4_x_vicuna/requirements.txt deleted file mode 100644 index 5e83b53a0d..0000000000 --- a/neurons/text/prompting/miners/huggingface/gpt4_x_vicuna/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -transformers>=4.28.0 -fschat -tokenizers>=0.13.3 -accelerate \ No newline at end of file diff --git a/neurons/text/prompting/miners/huggingface/guanaco/README.md b/neurons/text/prompting/miners/huggingface/guanaco/README.md deleted file mode 100644 index 5ecdb24207..0000000000 --- a/neurons/text/prompting/miners/huggingface/guanaco/README.md +++ /dev/null @@ -1,150 +0,0 @@ -# Guanaco Miner -timdettmers Guanaco Language Model Serving with BitTensor -This code is for running a language model powered by togethercomputer through the BitTensor framework. -Reference: [code](https://github.com/artidoro/qlora) - -# Example Usage -``` -python3 -m pip install -r neurons/text/prompting/miners/huggingface/guanaco/requirements.txt -python neurons/text/prompting/miners/huggingface/guanaco/neuron.py --guanaco.model_name timdettmers/guanaco-33b-merged --guanaco.device_map auto -``` - -# Full Usage -``` -usage: guanaco_miner.py [-h] [--guanaco.model_name GUANACO.MODEL_NAME] [--guanaco.api_key GUANACO.API_KEY] - [--guanaco.device GUANACO.DEVICE] [--guanaco.max_new_tokens GUANACO.MAX_NEW_TOKENS] - [--guanaco.temperature GUANACO.TEMPERATURE] [--guanaco.do_sample] - [--guanaco.repetition_penalty GUANACO.REPETITION_PENALTY] [--guanaco.do_prompt_injection] - [--guanaco.system_prompt GUANACO.SYSTEM_PROMPT] - [--guanaco.repetition-penalty GUANACO.REPETITION_PENALTY] [--guanaco.top_p GUANACO.TOP_P] - [--guanaco.top_k GUANACO.TOP_K] [--guanaco.load_in_8bit GUANACO.LOAD_IN_8BIT] - [--guanaco.device_map GUANACO.DEVICE_MAP] - [--guanaco.pad_tokens GUANACO.PAD_TOKENS [GUANACO.PAD_TOKENS ...]] [--netuid NETUID] - [--neuron.name NEURON.NAME] [--neuron.blocks_per_epoch NEURON.BLOCKS_PER_EPOCH] - [--neuron.no_set_weights] [--neuron.max_batch_size NEURON.MAX_BATCH_SIZE] - [--neuron.max_sequence_len NEURON.MAX_SEQUENCE_LEN] - [--neuron.blacklist.hotkeys [NEURON.BLACKLIST.HOTKEYS [NEURON.BLACKLIST.HOTKEYS ...]]] - [--neuron.blacklist.allow_non_registered] - [--neuron.blacklist.default_stake NEURON.BLACKLIST.DEFAULT_STAKE] - [--neuron.default_priority NEURON.DEFAULT_PRIORITY] [--wallet.name WALLET.NAME] - [--wallet.hotkey WALLET.HOTKEY] [--wallet.path WALLET.PATH] [--wallet._mock] - [--wallet.reregister WALLET.REREGISTER] [--axon.priority.max_workers AXON.PRIORITY.MAX_WORKERS] - [--axon.priority.maxsize AXON.PRIORITY.MAXSIZE] [--axon.port AXON.PORT] [--axon.ip AXON.IP] - [--axon.external_port AXON.EXTERNAL_PORT] [--axon.external_ip AXON.EXTERNAL_IP] - [--axon.max_workers AXON.MAX_WORKERS] [--axon.maximum_concurrent_rpcs AXON.MAXIMUM_CONCURRENT_RPCS] - [--subtensor.network SUBTENSOR.NETWORK] [--subtensor.chain_endpoint SUBTENSOR.CHAIN_ENDPOINT] - [--subtensor._mock] [--subtensor.register.num_processes SUBTENSOR.REGISTER.NUM_PROCESSES] - [--subtensor.register.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL] - [--subtensor.register.no_output_in_place] [--subtensor.register.verbose] - [--subtensor.register.cuda.use_cuda] [--subtensor.register.cuda.no_cuda] - [--subtensor.register.cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...]] - [--subtensor.register.cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB] [--logging.debug] [--logging.trace] - [--logging.record_log] [--logging.logging_dir LOGGING.LOGGING_DIR] [--config CONFIG] [--strict] - -optional arguments: - -h, --help show this help message and exit - --guanaco.model_name GUANACO.MODEL_NAME - Name or path of model to load - --guanaco.api_key GUANACO.API_KEY - huggingface api key - --guanaco.device GUANACO.DEVICE - Device to load model - --guanaco.max_new_tokens GUANACO.MAX_NEW_TOKENS - Max tokens for model output. - --guanaco.temperature GUANACO.TEMPERATURE - Sampling temperature of model - --guanaco.do_sample Whether to use multinomial sampling. - --guanaco.repetition_penalty GUANACO.REPETITION_PENALTY - Repetition penalty for model - --guanaco.do_prompt_injection - Whether to use a custom "system" prompt instead of the one sent by bittensor. - --guanaco.system_prompt GUANACO.SYSTEM_PROMPT - What prompt to replace the system prompt with - --guanaco.repetition-penalty GUANACO.REPETITION_PENALTY - Repetition penalty for greedy decoding. Between 1.0 and infinity. 1.0 means no penalty. Default: 1.0 - --guanaco.top_p GUANACO.TOP_P - Top-p (nucleus) sampling. Defaults to 1.0 (top-k sampling). Must be between 0.0 and 1.0. - --guanaco.top_k GUANACO.TOP_K - Top-k sampling. Defaults to 0 (no top-k sampling). Must be between 0 and 1000. - --guanaco.load_in_8bit GUANACO.LOAD_IN_8BIT - Load model in 8 bit precision - --guanaco.device_map GUANACO.DEVICE_MAP - Device map for model parallelism. - --guanaco.pad_tokens GUANACO.PAD_TOKENS [GUANACO.PAD_TOKENS ...] - A list of integers separated by spaces for the pad_tokens. - --netuid NETUID Subnet netuid - --neuron.name NEURON.NAME - Trials for this miner go in miner.root / (wallet_cold - wallet_hot) / miner.name - --neuron.blocks_per_epoch NEURON.BLOCKS_PER_EPOCH - Blocks until the miner sets weights on chain - --neuron.no_set_weights - If True, the model does not set weights. - --neuron.max_batch_size NEURON.MAX_BATCH_SIZE - The maximum batch size for forward requests. - --neuron.max_sequence_len NEURON.MAX_SEQUENCE_LEN - The maximum sequence length for forward requests. - --neuron.blacklist.hotkeys [NEURON.BLACKLIST.HOTKEYS [NEURON.BLACKLIST.HOTKEYS ...]] - To blacklist certain hotkeys - --neuron.blacklist.allow_non_registered - If True, the miner will allow non-registered hotkeys to mine. - --neuron.blacklist.default_stake NEURON.BLACKLIST.DEFAULT_STAKE - Set default stake for miners. - --neuron.default_priority NEURON.DEFAULT_PRIORITY - Set default priority for miners. - --wallet.name WALLET.NAME - The name of the wallet to unlock for running bittensor (name mock is reserved for mocking this - wallet) - --wallet.hotkey WALLET.HOTKEY - The name of wallet's hotkey. - --wallet.path WALLET.PATH - The path to your bittensor wallets - --wallet._mock To turn on wallet mocking for testing purposes. - --wallet.reregister WALLET.REREGISTER - Whether to reregister the wallet if it is not already registered. - --axon.priority.max_workers AXON.PRIORITY.MAX_WORKERS - maximum number of threads in thread pool - --axon.priority.maxsize AXON.PRIORITY.MAXSIZE - maximum size of tasks in priority queue - --axon.port AXON.PORT - The local port this axon endpoint is bound to. i.e. 8091 - --axon.ip AXON.IP The local ip this axon binds to. ie. [::] - --axon.external_port AXON.EXTERNAL_PORT - The public port this axon broadcasts to the network. i.e. 8091 - --axon.external_ip AXON.EXTERNAL_IP - The external ip this axon broadcasts to the network to. ie. [::] - --axon.max_workers AXON.MAX_WORKERS - The maximum number connection handler threads working simultaneously on this endpoint. The grpc - server distributes new worker threads to service requests up to this number. - --axon.maximum_concurrent_rpcs AXON.MAXIMUM_CONCURRENT_RPCS - Maximum number of allowed active connections - --subtensor.network SUBTENSOR.NETWORK - The subtensor network flag. The likely choices are: -- finney (main network) -- local (local running - network) -- mock (creates a mock connection (for testing)) If this option is set it overloads - subtensor.chain_endpoint with an entry point node from that network. - --subtensor.chain_endpoint SUBTENSOR.CHAIN_ENDPOINT - The subtensor endpoint flag. If set, overrides the --network flag. - --subtensor._mock To turn on subtensor mocking for testing purposes. - --subtensor.register.num_processes SUBTENSOR.REGISTER.NUM_PROCESSES, -n SUBTENSOR.REGISTER.NUM_PROCESSES - Number of processors to use for registration - --subtensor.register.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, --subtensor.register.cuda.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, --cuda.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, -u SUBTENSOR.REGISTER.UPDATE_INTERVAL - The number of nonces to process before checking for next block during registration - --subtensor.register.no_output_in_place, --no_output_in_place - Whether to not ouput the registration statistics in-place. Set flag to disable output in-place. - --subtensor.register.verbose - Whether to ouput the registration statistics verbosely. - --subtensor.register.cuda.use_cuda, --cuda, --cuda.use_cuda - Set flag to use CUDA to register. - --subtensor.register.cuda.no_cuda, --no_cuda, --cuda.no_cuda - Set flag to not use CUDA for registration - --subtensor.register.cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...], --cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...] - Set the CUDA device id(s). Goes by the order of speed. (i.e. 0 is the fastest). - --subtensor.register.cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB, --cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB - Set the number of Threads Per Block for CUDA. - --logging.debug Turn on bittensor debugging information - --logging.trace Turn on bittensor trace level information - --logging.record_log Turns on logging to file. - --logging.logging_dir LOGGING.LOGGING_DIR - Logging default root directory. - --config CONFIG If set, defaults are overridden by passed file. - --strict If flagged, config will check that only exact arguemnts have been set. - ``` \ No newline at end of file diff --git a/neurons/text/prompting/miners/huggingface/guanaco/neuron.py b/neurons/text/prompting/miners/huggingface/guanaco/neuron.py deleted file mode 100644 index 1d09f4afec..0000000000 --- a/neurons/text/prompting/miners/huggingface/guanaco/neuron.py +++ /dev/null @@ -1,74 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2023 Yuma Rao - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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 -from typing import List, Dict -from transformers import AutoTokenizer, AutoModelForCausalLM - - -class GuanacoMiner(bittensor.HuggingFaceMiner): - arg_prefix: str = "guanaco" - assistant_label: str = "### Assistant:" - user_label: str = "### Human:" - system_label: str = "" - - def load_tokenizer(self): - return AutoTokenizer.from_pretrained(self.config.guanaco.model_name) - - def load_model(self): - return AutoModelForCausalLM.from_pretrained( - self.config.guanaco.model_name, - torch_dtype=torch.float16, - low_cpu_mem_usage=True, - device_map=self.config.guanaco.device_map, - ) - - def forward(self, messages: List[Dict[str, str]]) -> str: - history = self.process_history(messages) - prompt = history + self.assistant_label - - generate_kwargs = dict( - temperature=self.config.guanaco.temperature, - max_new_tokens=self.config.guanaco.max_new_tokens, - top_p=self.config.guanaco.top_p, - repetition_penalty=self.config.guanaco.repetition_penalty, - do_sample=self.config.guanaco.do_sample, - ) - if ( - "33B" in self.config.guanaco.model_name - ): # Tim Dettmers 33B model-specific parameters - generate_kwargs["truncate"] = 999 - generate_kwargs["seed"] = 42 - - input_ids = self.tokenizer.encode(prompt, return_tensors="pt").to( - self.config.guanaco.device - ) - output = self.model.generate(input_ids, **generate_kwargs) - generated_text = self.tokenizer.decode( - output[0][input_ids.shape[1] :], skip_special_tokens=True - ) - generation = generated_text.split(self.assistant_label)[0].strip() - - bittensor.logging.debug("Message: " + str(messages)) - bittensor.logging.debug("Generation: " + str(generation)) - return generation - - -if __name__ == "__main__": - bittensor.utils.version_checking() - GuanacoMiner().run() diff --git a/neurons/text/prompting/miners/huggingface/guanaco/requirements.txt b/neurons/text/prompting/miners/huggingface/guanaco/requirements.txt deleted file mode 100644 index 33059ec77c..0000000000 --- a/neurons/text/prompting/miners/huggingface/guanaco/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -transformers>=4.29.2 -accelerate \ No newline at end of file diff --git a/neurons/text/prompting/miners/huggingface/koala/README.md b/neurons/text/prompting/miners/huggingface/koala/README.md deleted file mode 100644 index 6e56fed45d..0000000000 --- a/neurons/text/prompting/miners/huggingface/koala/README.md +++ /dev/null @@ -1,175 +0,0 @@ -# Koala Miner -Koala Language Model Serving with BitTensor -This code is for running the Koala model through the BitTensor framework. - -# Overview - -## Contents - -- [Installing Dependencies](#installing-Dependencies) -- [Converting Weights Into Model](#converting-weights-into-model) -- [Starting Miner](#starting-miner) - - -# Installing Dependencies - -``` -python3 -m pip install -r neurons/text/prompting/miners/huggingface/koala/requirements.txt -``` - -# Converting Weights Into Model -If you already have a converted checkpoint of the model, you can skip this step. - -Original documentation for creating the model from weights can be found [here](https://github.com/young-geng/EasyLM/blob/main/docs/koala.md) - -## Obtaining the Wegith Diff of Koala -Due to the licence of the LLaMA model, the fine-tuned -Koala model weights can not be directly released. Instead, the diff of weights, which can be used -to recover the Koala model weights with the original LLaMA model weights, is released. The diff -weights can be downloaded from the following sources: -* [HuggingFace Hub](https://huggingface.co/young-geng/koala/tree/main). -* [Google Drive](https://drive.google.com/drive/folders/10f7wrlAFoPIy-TECHsx9DKIvbQYunCfl?usp=sharing). - -## Recovering the Koala Model Weights -The first step of recovering the Koala model weights is to obtain the original -LLaMA model weights and convert it to EasyLM checkpoint format. To convert the weights, -use the following command: - -``` shell -python -m EasyLM.models.llama.convert_torch_to_easylm \ - --checkpoint_dir='path/to/torch/llama/checkpoint/directory' \ - --output_file='path/to/output/easylm/checkpoint/file' \ - --streaming=True -``` - -This script will convert the official torch checkpoint from Meta to the -streaming checkpoint format used by EasyLM. For more information -about the checkpoint format of EasyLM, see [the checkpointing documentation](checkpointing.md). - - -After converting the original LLaMA model weights, you can recover the Koala -model weights with the following command: - -``` shell -python -m EasyLM.scripts.diff_checkpoint \ - --recover_diff=True \ - --load_base_checkpoint='params::path/to/llama/checkpoint/file' \ - --load_target_checkpoint='params::path/to/koala/diff/checkpoint/file' \ - --output_file='path/to/output/checkpoint/file' \ - --streaming=True -``` - - -# Starting Miner -``` -python3 neurons/text/prompting/miners/huggingface/koala_miner.py --koala.model_name TheBloke/koala-7B-HF -``` - -# Full Usage -``` -usage: neuron.py [-h] [--koala.model_name KOALA.MODEL_NAME] [--koala.device KOALA.DEVICE] [--koala.max_new_tokens KOALA.MAX_NEW_TOKENS] - [--koala.temperature KOALA.TEMPERATURE] [--koala.do_sample] [--netuid NETUID] [--neuron.name NEURON.NAME] - [--neuron.blocks_per_epoch NEURON.BLOCKS_PER_EPOCH] [--neuron.no_set_weights] - [--neuron.max_batch_size NEURON.MAX_BATCH_SIZE] [--neuron.max_sequence_len NEURON.MAX_SEQUENCE_LEN] - [--neuron.blacklist.hotkeys [NEURON.BLACKLIST.HOTKEYS ...]] [--neuron.blacklist.allow_non_registered] - [--neuron.blacklist.default_stake NEURON.BLACKLIST.DEFAULT_STAKE] [--neuron.default_priority NEURON.DEFAULT_PRIORITY] - [--wallet.name WALLET.NAME] [--wallet.hotkey WALLET.HOTKEY] [--wallet.path WALLET.PATH] [--wallet._mock] - [--wallet.reregister WALLET.REREGISTER] [--axon.priority.max_workers AXON.PRIORITY.MAX_WORKERS] - [--axon.priority.maxsize AXON.PRIORITY.MAXSIZE] [--axon.port AXON.PORT] [--axon.ip AXON.IP] - [--axon.external_port AXON.EXTERNAL_PORT] [--axon.external_ip AXON.EXTERNAL_IP] [--axon.max_workers AXON.MAX_WORKERS] - [--axon.maximum_concurrent_rpcs AXON.MAXIMUM_CONCURRENT_RPCS] [--subtensor.network SUBTENSOR.NETWORK] - [--subtensor.chain_endpoint SUBTENSOR.CHAIN_ENDPOINT] [--subtensor._mock] - [--subtensor.register.num_processes SUBTENSOR.REGISTER.NUM_PROCESSES] - [--subtensor.register.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL] [--subtensor.register.no_output_in_place] - [--subtensor.register.verbose] [--subtensor.register.cuda.use_cuda] [--subtensor.register.cuda.no_cuda] - [--subtensor.register.cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...]] - [--subtensor.register.cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB] [--logging.debug] [--logging.trace] [--logging.record_log] - [--logging.logging_dir LOGGING.LOGGING_DIR] [--metagraph._mock] [--config CONFIG] [--strict] - -optional arguments: - -h, --help show this help message and exit - --neoxt.model_name NEOXT.MODEL_NAME - Name/path of model to load of model to load - --koala.device KOALA.DEVICE - Device to load model - --koala.max_new_tokens KOALA.MAX_NEW_TOKENS - Max tokens for model output. - --koala.temperature KOALA.TEMPERATURE - Sampling temperature of model - --koala.do_sample Whether to use sampling or not (if not, uses greedy decoding). - --netuid NETUID Subnet netuid - --neuron.name NEURON.NAME - Trials for this miner go in miner.root / (wallet_cold - wallet_hot) / miner.name - --neuron.blocks_per_epoch NEURON.BLOCKS_PER_EPOCH - Blocks until the miner sets weights on chain - --neuron.no_set_weights - If True, the model does not set weights. - --neuron.max_batch_size NEURON.MAX_BATCH_SIZE - The maximum batch size for forward requests. - --neuron.max_sequence_len NEURON.MAX_SEQUENCE_LEN - The maximum sequence length for forward requests. - --neuron.blacklist.hotkeys [NEURON.BLACKLIST.HOTKEYS ...] - To blacklist certain hotkeys - --neuron.blacklist.allow_non_registered - If True, the miner will allow non-registered hotkeys to mine. - --neuron.blacklist.default_stake NEURON.BLACKLIST.DEFAULT_STAKE - Set default stake for miners. - --neuron.default_priority NEURON.DEFAULT_PRIORITY - Set default priority for miners. - --wallet.name WALLET.NAME - The name of the wallet to unlock for running bittensor (name mock is reserved for mocking this wallet) - --wallet.hotkey WALLET.HOTKEY - The name of wallet's hotkey. - --wallet.path WALLET.PATH - The path to your bittensor wallets - --wallet._mock To turn on wallet mocking for testing purposes. - --wallet.reregister WALLET.REREGISTER - Whether to reregister the wallet if it is not already registered. - --axon.priority.max_workers AXON.PRIORITY.MAX_WORKERS - maximum number of threads in thread pool - --axon.priority.maxsize AXON.PRIORITY.MAXSIZE - maximum size of tasks in priority queue - --axon.port AXON.PORT - The local port this axon endpoint is bound to. i.e. 8091 - --axon.ip AXON.IP The local ip this axon binds to. ie. [::] - --axon.external_port AXON.EXTERNAL_PORT - The public port this axon broadcasts to the network. i.e. 8091 - --axon.external_ip AXON.EXTERNAL_IP - The external ip this axon broadcasts to the network to. ie. [::] - --axon.max_workers AXON.MAX_WORKERS - The maximum number connection handler threads working simultaneously on this endpoint. The grpc server distributes - new worker threads to service requests up to this number. - --axon.maximum_concurrent_rpcs AXON.MAXIMUM_CONCURRENT_RPCS - Maximum number of allowed active connections - --subtensor.network SUBTENSOR.NETWORK - The subtensor network flag. The likely choices are: -- finney (main network) -- local (local running network) -- - mock (creates a mock connection (for testing)) If this option is set it overloads subtensor.chain_endpoint with an - entry point node from that network. - --subtensor.chain_endpoint SUBTENSOR.CHAIN_ENDPOINT - The subtensor endpoint flag. If set, overrides the --network flag. - --subtensor._mock To turn on subtensor mocking for testing purposes. - --subtensor.register.num_processes SUBTENSOR.REGISTER.NUM_PROCESSES, -n SUBTENSOR.REGISTER.NUM_PROCESSES - Number of processors to use for registration - --subtensor.register.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, --subtensor.register.cuda.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, --cuda.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, -u SUBTENSOR.REGISTER.UPDATE_INTERVAL - The number of nonces to process before checking for next block during registration - --subtensor.register.no_output_in_place, --no_output_in_place - Whether to not ouput the registration statistics in-place. Set flag to disable output in-place. - --subtensor.register.verbose - Whether to ouput the registration statistics verbosely. - --subtensor.register.cuda.use_cuda, --cuda, --cuda.use_cuda - Set flag to use CUDA to register. - --subtensor.register.cuda.no_cuda, --no_cuda, --cuda.no_cuda - Set flag to not use CUDA for registration - --subtensor.register.cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...], --cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...] - Set the CUDA device id(s). Goes by the order of speed. (i.e. 0 is the fastest). - --subtensor.register.cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB, --cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB - Set the number of Threads Per Block for CUDA. - --logging.debug Turn on bittensor debugging information - --logging.trace Turn on bittensor trace level information - --logging.record_log Turns on logging to file. - --logging.logging_dir LOGGING.LOGGING_DIR - Logging default root directory. - --metagraph._mock To turn on metagraph mocking for testing purposes. - --config CONFIG If set, defaults are overridden by passed file. - --strict If flagged, config will check that only exact arguemnts have been set. -``` \ No newline at end of file diff --git a/neurons/text/prompting/miners/huggingface/koala/neuron.py b/neurons/text/prompting/miners/huggingface/koala/neuron.py deleted file mode 100644 index 8f5d7f3670..0000000000 --- a/neurons/text/prompting/miners/huggingface/koala/neuron.py +++ /dev/null @@ -1,66 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2023 Opentensor Foundation - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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 -from typing import List, Dict -from transformers import AutoTokenizer, AutoModelForCausalLM - - -class KoalaMiner(bittensor.HuggingFaceMiner): - arg_prefix: str = "koala" - assistant_label: str = "GPT:" - user_label: str = "USER:" - system_label: str = "" - - def load_tokenizer(self): - return AutoTokenizer.from_pretrained( - self.config.koala.model_name, use_fast=False - ) - - def load_model(self): - return AutoModelForCausalLM.from_pretrained( - self.config.koala.model_name, - torch_dtype=torch.float16, - low_cpu_mem_usage=True, - ) - - def forward(self, messages: List[Dict[str, str]]) -> str: - history = self._process_history(messages) - prompt = history + self.system_label - input_ids = self.tokenizer.encode(prompt, return_tensors="pt").to( - self.config.koala.device - ) - output = self.model.generate( - input_ids, - max_length=input_ids.shape[1] + self.config.koala.max_new_tokens, - temperature=self.config.koala.temperature, - do_sample=self.config.koala.do_sample, - pad_token_id=self.tokenizer.eos_token_id, - ) - generation = self.tokenizer.decode( - output[0][input_ids.shape[1] :], skip_special_tokens=True - ) - - bittensor.logging.debug("Message: " + str(messages)) - bittensor.logging.debug("Generation: " + str(generation)) - return generation - - -if __name__ == "__main__": - bittensor.utils.version_checking() - KoalaMiner().run() diff --git a/neurons/text/prompting/miners/huggingface/koala/requirements.txt b/neurons/text/prompting/miners/huggingface/koala/requirements.txt deleted file mode 100644 index bd7d62c8ca..0000000000 --- a/neurons/text/prompting/miners/huggingface/koala/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -transformers>=4.28.0 -fschat -accelerate \ No newline at end of file diff --git a/neurons/text/prompting/miners/huggingface/mpt_chat/README.md b/neurons/text/prompting/miners/huggingface/mpt_chat/README.md deleted file mode 100644 index c80893753e..0000000000 --- a/neurons/text/prompting/miners/huggingface/mpt_chat/README.md +++ /dev/null @@ -1,139 +0,0 @@ -## MPT_Chat Miner -MPT_Chat Language Model Serving with BitTensor -This code is for running the Mpt_chat by MosaicML model through the BitTensor framework. - -# Overview - -## Contents - -- [Licence](#Licence) -- [Installing Dependencies](#installing-dependencies) -- [Starting Miner](#starting-miner) - - -# Licence -CC-By-NC-SA-4.0 (non-commercial use only) - - -# Installing Dependencies - -``` -python3 -m pip install -r neurons/text/prompting/miners/huggingface/mpt_chat/requirements.txt -``` - -# Starting Miner -To start the miner, all you need to do is to specify the path to the model, or the name on Huggingface hub, and it will be downloaded. - -You can find different model checkpoints by searching Huggingface, or by looking at MosaicML's Huggingface page https://huggingface.co/NousResearch -``` -python3 neurons/text/prompting/miners/huggingface/mpt_chat/neuron.py --mpt_chat.model_name MPT_CHAT.MODEL_NAME_OR_PATH -``` - -# Full Usage -``` -usage: neuron.py [-h] [--mpt_chat.model_name MPT_CHAT.MODEL_NAME] [--mpt_chat.device MPT_CHAT.DEVICE] [--mpt_chat.max_new_tokens MPT_CHAT.MAX_NEW_TOKENS] - [--mpt_chat.temperature MPT_CHAT.TEMPERATURE] [--mpt_chat.do_sample] [--netuid NETUID] [--neuron.name NEURON.NAME] - [--neuron.blocks_per_epoch NEURON.BLOCKS_PER_EPOCH] [--neuron.no_set_weights] - [--neuron.max_batch_size NEURON.MAX_BATCH_SIZE] [--neuron.max_sequence_len NEURON.MAX_SEQUENCE_LEN] - [--neuron.blacklist.hotkeys [NEURON.BLACKLIST.HOTKEYS ...]] [--neuron.blacklist.allow_non_registered] - [--neuron.blacklist.default_stake NEURON.BLACKLIST.DEFAULT_STAKE] [--neuron.default_priority NEURON.DEFAULT_PRIORITY] - [--wallet.name WALLET.NAME] [--wallet.hotkey WALLET.HOTKEY] [--wallet.path WALLET.PATH] [--wallet._mock] - [--wallet.reregister WALLET.REREGISTER] [--axon.priority.max_workers AXON.PRIORITY.MAX_WORKERS] - [--axon.priority.maxsize AXON.PRIORITY.MAXSIZE] [--axon.port AXON.PORT] [--axon.ip AXON.IP] - [--axon.external_port AXON.EXTERNAL_PORT] [--axon.external_ip AXON.EXTERNAL_IP] [--axon.max_workers AXON.MAX_WORKERS] - [--axon.maximum_concurrent_rpcs AXON.MAXIMUM_CONCURRENT_RPCS] [--subtensor.network SUBTENSOR.NETWORK] - [--subtensor.chain_endpoint SUBTENSOR.CHAIN_ENDPOINT] [--subtensor._mock] - [--subtensor.register.num_processes SUBTENSOR.REGISTER.NUM_PROCESSES] - [--subtensor.register.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL] [--subtensor.register.no_output_in_place] - [--subtensor.register.verbose] [--subtensor.register.cuda.use_cuda] [--subtensor.register.cuda.no_cuda] - [--subtensor.register.cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...]] - [--subtensor.register.cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB] [--logging.debug] [--logging.trace] [--logging.record_log] - [--logging.logging_dir LOGGING.LOGGING_DIR] [--metagraph._mock] [--config CONFIG] [--strict] - -optional arguments: - -h, --help show this help message and exit - --neoxt.model_name NEOXT.MODEL_NAME - Name/path of model to load of model to load - --mpt_chat.device MPT_CHAT.DEVICE - Device to load model - --mpt_chat.max_new_tokens MPT_CHAT.MAX_NEW_TOKENS - Max tokens for model output. - --mpt_chat.temperature MPT_CHAT.TEMPERATURE - Sampling temperature of model - --mpt_chat.do_sample Whether to use sampling or not (if not, uses greedy decoding). - --netuid NETUID Subnet netuid - --neuron.name NEURON.NAME - Trials for this miner go in miner.root / (wallet_cold - wallet_hot) / miner.name - --neuron.blocks_per_epoch NEURON.BLOCKS_PER_EPOCH - Blocks until the miner sets weights on chain - --neuron.no_set_weights - If True, the model does not set weights. - --neuron.max_batch_size NEURON.MAX_BATCH_SIZE - The maximum batch size for forward requests. - --neuron.max_sequence_len NEURON.MAX_SEQUENCE_LEN - The maximum sequence length for forward requests. - --neuron.blacklist.hotkeys [NEURON.BLACKLIST.HOTKEYS ...] - To blacklist certain hotkeys - --neuron.blacklist.allow_non_registered - If True, the miner will allow non-registered hotkeys to mine. - --neuron.blacklist.default_stake NEURON.BLACKLIST.DEFAULT_STAKE - Set default stake for miners. - --neuron.default_priority NEURON.DEFAULT_PRIORITY - Set default priority for miners. - --wallet.name WALLET.NAME - The name of the wallet to unlock for running bittensor (name mock is reserved for mocking this wallet) - --wallet.hotkey WALLET.HOTKEY - The name of wallet's hotkey. - --wallet.path WALLET.PATH - The path to your bittensor wallets - --wallet._mock To turn on wallet mocking for testing purposes. - --wallet.reregister WALLET.REREGISTER - Whether to reregister the wallet if it is not already registered. - --axon.priority.max_workers AXON.PRIORITY.MAX_WORKERS - maximum number of threads in thread pool - --axon.priority.maxsize AXON.PRIORITY.MAXSIZE - maximum size of tasks in priority queue - --axon.port AXON.PORT - The local port this axon endpoint is bound to. i.e. 8091 - --axon.ip AXON.IP The local ip this axon binds to. ie. [::] - --axon.external_port AXON.EXTERNAL_PORT - The public port this axon broadcasts to the network. i.e. 8091 - --axon.external_ip AXON.EXTERNAL_IP - The external ip this axon broadcasts to the network to. ie. [::] - --axon.max_workers AXON.MAX_WORKERS - The maximum number connection handler threads working simultaneously on this endpoint. The grpc server distributes - new worker threads to service requests up to this number. - --axon.maximum_concurrent_rpcs AXON.MAXIMUM_CONCURRENT_RPCS - Maximum number of allowed active connections - --subtensor.network SUBTENSOR.NETWORK - The subtensor network flag. The likely choices are: -- finney (main network) -- local (local running network) -- - mock (creates a mock connection (for testing)) If this option is set it overloads subtensor.chain_endpoint with an - entry point node from that network. - --subtensor.chain_endpoint SUBTENSOR.CHAIN_ENDPOINT - The subtensor endpoint flag. If set, overrides the --network flag. - --subtensor._mock To turn on subtensor mocking for testing purposes. - --subtensor.register.num_processes SUBTENSOR.REGISTER.NUM_PROCESSES, -n SUBTENSOR.REGISTER.NUM_PROCESSES - Number of processors to use for registration - --subtensor.register.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, --subtensor.register.cuda.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, --cuda.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, -u SUBTENSOR.REGISTER.UPDATE_INTERVAL - The number of nonces to process before checking for next block during registration - --subtensor.register.no_output_in_place, --no_output_in_place - Whether to not ouput the registration statistics in-place. Set flag to disable output in-place. - --subtensor.register.verbose - Whether to ouput the registration statistics verbosely. - --subtensor.register.cuda.use_cuda, --cuda, --cuda.use_cuda - Set flag to use CUDA to register. - --subtensor.register.cuda.no_cuda, --no_cuda, --cuda.no_cuda - Set flag to not use CUDA for registration - --subtensor.register.cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...], --cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...] - Set the CUDA device id(s). Goes by the order of speed. (i.e. 0 is the fastest). - --subtensor.register.cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB, --cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB - Set the number of Threads Per Block for CUDA. - --logging.debug Turn on bittensor debugging information - --logging.trace Turn on bittensor trace level information - --logging.record_log Turns on logging to file. - --logging.logging_dir LOGGING.LOGGING_DIR - Logging default root directory. - --metagraph._mock To turn on metagraph mocking for testing purposes. - --config CONFIG If set, defaults are overridden by passed file. - --strict If flagged, config will check that only exact arguemnts have been set. -``` \ No newline at end of file diff --git a/neurons/text/prompting/miners/huggingface/mpt_chat/neuron.py b/neurons/text/prompting/miners/huggingface/mpt_chat/neuron.py deleted file mode 100644 index 525dab6836..0000000000 --- a/neurons/text/prompting/miners/huggingface/mpt_chat/neuron.py +++ /dev/null @@ -1,96 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2023 Yuma Rao - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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 argparse -import bittensor -from typing import List, Dict -from transformers import AutoTokenizer, AutoModelForCausalLM, AutoConfig - - -class Mpt_chatMiner(bittensor.HuggingFaceMiner): - arg_prefix: str = "mpt_chat" - system_label: str = "<|im_start|>system\n" - user_label: str = "<|im_start|>user\n" - assistant_label: str = "<|im_start|>assistant\n" - - @classmethod - def add_args(cls, parser: argparse.ArgumentParser): - parser.add_argument( - "--mpt_chat.tokenizer_name", - type=str, - required=False, - help="Name/path of model to load", - default="EleutherAI/gpt-neox-20b", - ) - parser.add_argument( - "--mpt_chat.use_triton", - action="store_true", - default=False, - help="Whether to use a triton to speed up inference", - ) - - def load_tokenizer(self): - return AutoTokenizer.from_pretrained(self.config.mpt_chat.tokenizer_name) - - def load_model(self): - config = AutoConfig.from_pretrained( - "mosaicml/mpt-7b-chat", trust_remote_code=True - ) - - if self.config.mpt_chat.use_triton: - config.attn_config["attn_impl"] = "triton" - - model = AutoModelForCausalLM.from_pretrained( - self.config.mpt_chat.model_name, - torch_dtype=torch.float16, - low_cpu_mem_usage=True, - trust_remote_code=True, - config=config, - ) - return model - - def forward(self, messages: List[Dict[str, str]]) -> str: - history = self.process_history(messages) - prompt = history + self.assistant_label - - input_ids = self.tokenizer.encode(prompt, return_tensors="pt").to( - self.config.mpt_chat.device - ) - - output = self.model.generate( - input_ids, - max_length=input_ids.shape[1] + self.config.mpt_chat.max_new_tokens, - temperature=self.config.mpt_chat.temperature, - do_sample=self.config.mpt_chat.do_sample, - pad_token_id=self.tokenizer.eos_token_id, - ) - - generation = self.tokenizer.decode( - output[0][input_ids.shape[1] :], skip_special_tokens=False - ).strip() - generation = generation.split("<|endoftext|>")[0] - - bittensor.logging.debug("Message: " + str(messages)) - bittensor.logging.debug("Prompt: " + str(prompt)) - bittensor.logging.debug("Generation: " + str(generation.replace("<", "-"))) - return generation - - -if __name__ == "__main__": - bittensor.utils.version_checking() - Mpt_chatMiner().run() diff --git a/neurons/text/prompting/miners/huggingface/mpt_chat/requirements.txt b/neurons/text/prompting/miners/huggingface/mpt_chat/requirements.txt deleted file mode 100644 index 406fd1d021..0000000000 --- a/neurons/text/prompting/miners/huggingface/mpt_chat/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -transformers>=4.28.0 -fschat -tokenizers>=0.13.3 -accelerate -einops \ No newline at end of file diff --git a/neurons/text/prompting/miners/huggingface/neoxt/README.md b/neurons/text/prompting/miners/huggingface/neoxt/README.md deleted file mode 100644 index ffb387c9e0..0000000000 --- a/neurons/text/prompting/miners/huggingface/neoxt/README.md +++ /dev/null @@ -1,136 +0,0 @@ -## Neoxt Miner -togethercomputer/GPT-NeoXT-Chat-Base-20B Language Model Serving with BitTensor -This code is for running a language model powered by togethercomputer through the BitTensor framework. - -# Example Usage -``` -python3 -m pip install -r neurons/text/prompting/miners/huggingface/neoxt/requirements.txt -python3 neurons/text/prompting/miners/huggingface/neoxt/neuron.py --neoxt.model_name togethercomputer/GPT-NeoXT-Chat-Base-20B -``` - -# Full Usage -``` -usage: neoxt_miner.py [-h] --neoxt.model_name NEOXT.MODEL_NAME [--neoxt.device NEOXT.DEVICE] [--neoxt.max_new_tokens NEOXT.MAX_NEW_TOKENS] - [--neoxt.temperature NEOXT.TEMPERATURE] [--neoxt.do_sample] [--neoxt.repetition_penalty NEOXT.REPETITION_PENALTY] - [--neoxt.do_prompt_injection] [--neoxt.system_prompt NEOXT.SYSTEM_PROMPT] - [--neoxt.repetition-penalty NEOXT.REPETITION_PENALTY] [--neoxt.top_p NEOXT.TOP_P] [--neoxt.top_k NEOXT.TOP_K] - [--neoxt.load_in_8bit NEOXT.LOAD_IN_8BIT] [--neoxt.pad_tokens NEOXT.PAD_TOKENS [NEOXT.PAD_TOKENS ...]] [--netuid NETUID] - [--neuron.name NEURON.NAME] [--neuron.blocks_per_epoch NEURON.BLOCKS_PER_EPOCH] [--neuron.no_set_weights] - [--neuron.max_batch_size NEURON.MAX_BATCH_SIZE] [--neuron.max_sequence_len NEURON.MAX_SEQUENCE_LEN] - [--neuron.blacklist.hotkeys [NEURON.BLACKLIST.HOTKEYS [NEURON.BLACKLIST.HOTKEYS ...]]] - [--neuron.blacklist.allow_non_registered] [--neuron.blacklist.default_stake NEURON.BLACKLIST.DEFAULT_STAKE] - [--neuron.default_priority NEURON.DEFAULT_PRIORITY] [--wallet.name WALLET.NAME] [--wallet.hotkey WALLET.HOTKEY] - [--wallet.path WALLET.PATH] [--wallet._mock] [--wallet.reregister WALLET.REREGISTER] - [--axon.priority.max_workers AXON.PRIORITY.MAX_WORKERS] [--axon.priority.maxsize AXON.PRIORITY.MAXSIZE] - [--axon.port AXON.PORT] [--axon.ip AXON.IP] [--axon.external_port AXON.EXTERNAL_PORT] [--axon.external_ip AXON.EXTERNAL_IP] - [--axon.max_workers AXON.MAX_WORKERS] [--axon.maximum_concurrent_rpcs AXON.MAXIMUM_CONCURRENT_RPCS] - [--subtensor.network SUBTENSOR.NETWORK] [--subtensor.chain_endpoint SUBTENSOR.CHAIN_ENDPOINT] [--subtensor._mock] - [--subtensor.register.num_processes SUBTENSOR.REGISTER.NUM_PROCESSES] - [--subtensor.register.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL] [--subtensor.register.no_output_in_place] - [--subtensor.register.verbose] [--subtensor.register.cuda.use_cuda] [--subtensor.register.cuda.no_cuda] - [--subtensor.register.cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...]] - [--subtensor.register.cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB] [--logging.debug] [--logging.trace] [--logging.record_log] - [--logging.logging_dir LOGGING.LOGGING_DIR] [--config CONFIG] [--strict] - -optional arguments: - -h, --help show this help message and exit - --neoxt.model_name NEOXT.MODEL_NAME - Name or path of model to load - --neoxt.device NEOXT.DEVICE - Device to load model - --neoxt.max_new_tokens NEOXT.MAX_NEW_TOKENS - Max tokens for model output. - --neoxt.temperature NEOXT.TEMPERATURE - Sampling temperature of model - --neoxt.do_sample Whether to use multinomial sampling. - --neoxt.repetition_penalty NEOXT.REPETITION_PENALTY - Repetition penalty for model - --neoxt.do_prompt_injection - Whether to use a custom "system" prompt instead of the one sent by bittensor. - --neoxt.system_prompt NEOXT.SYSTEM_PROMPT - What prompt to replace the system prompt with - --neoxt.repetition-penalty NEOXT.REPETITION_PENALTY - Repetition penalty for greedy decoding. Between 1.0 and infinity. 1.0 means no penalty. Default: 1.0 - --neoxt.top_p NEOXT.TOP_P - Top-p (nucleus) sampling. Defaults to 1.0 (top-k sampling). Must be between 0.0 and 1.0. - --neoxt.top_k NEOXT.TOP_K - Top-k sampling. Defaults to 0 (no top-k sampling). Must be between 0 and 1000. - --neoxt.load_in_8bit NEOXT.LOAD_IN_8BIT - Load model in 8 bit precision - --neoxt.pad_tokens NEOXT.PAD_TOKENS [NEOXT.PAD_TOKENS ...] - A list of integers separated by spaces for the pad_tokens. - --netuid NETUID Subnet netuid - --neuron.name NEURON.NAME - Trials for this miner go in miner.root / (wallet_cold - wallet_hot) / miner.name - --neuron.blocks_per_epoch NEURON.BLOCKS_PER_EPOCH - Blocks until the miner sets weights on chain - --neuron.no_set_weights - If True, the model does not set weights. - --neuron.max_batch_size NEURON.MAX_BATCH_SIZE - The maximum batch size for forward requests. - --neuron.max_sequence_len NEURON.MAX_SEQUENCE_LEN - The maximum sequence length for forward requests. - --neuron.blacklist.hotkeys [NEURON.BLACKLIST.HOTKEYS [NEURON.BLACKLIST.HOTKEYS ...]] - To blacklist certain hotkeys - --neuron.blacklist.allow_non_registered - If True, the miner will allow non-registered hotkeys to mine. - --neuron.blacklist.default_stake NEURON.BLACKLIST.DEFAULT_STAKE - Set default stake for miners. - --neuron.default_priority NEURON.DEFAULT_PRIORITY - Set default priority for miners. - --wallet.name WALLET.NAME - The name of the wallet to unlock for running bittensor (name mock is reserved for mocking this wallet) - --wallet.hotkey WALLET.HOTKEY - The name of wallet's hotkey. - --wallet.path WALLET.PATH - The path to your bittensor wallets - --wallet._mock To turn on wallet mocking for testing purposes. - --wallet.reregister WALLET.REREGISTER - Whether to reregister the wallet if it is not already registered. - --axon.priority.max_workers AXON.PRIORITY.MAX_WORKERS - maximum number of threads in thread pool - --axon.priority.maxsize AXON.PRIORITY.MAXSIZE - maximum size of tasks in priority queue - --axon.port AXON.PORT - The local port this axon endpoint is bound to. i.e. 8091 - --axon.ip AXON.IP The local ip this axon binds to. ie. [::] - --axon.external_port AXON.EXTERNAL_PORT - The public port this axon broadcasts to the network. i.e. 8091 - --axon.external_ip AXON.EXTERNAL_IP - The external ip this axon broadcasts to the network to. ie. [::] - --axon.max_workers AXON.MAX_WORKERS - The maximum number connection handler threads working simultaneously on this endpoint. The grpc server distributes new - worker threads to service requests up to this number. - --axon.maximum_concurrent_rpcs AXON.MAXIMUM_CONCURRENT_RPCS - Maximum number of allowed active connections - --subtensor.network SUBTENSOR.NETWORK - The subtensor network flag. The likely choices are: -- finney (main network) -- local (local running network) -- mock - (creates a mock connection (for testing)) If this option is set it overloads subtensor.chain_endpoint with an entry point - node from that network. - --subtensor.chain_endpoint SUBTENSOR.CHAIN_ENDPOINT - The subtensor endpoint flag. If set, overrides the --network flag. - --subtensor._mock To turn on subtensor mocking for testing purposes. - --subtensor.register.num_processes SUBTENSOR.REGISTER.NUM_PROCESSES, -n SUBTENSOR.REGISTER.NUM_PROCESSES - Number of processors to use for registration - --subtensor.register.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, --subtensor.register.cuda.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, --cuda.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, -u SUBTENSOR.REGISTER.UPDATE_INTERVAL - The number of nonces to process before checking for next block during registration - --subtensor.register.no_output_in_place, --no_output_in_place - Whether to not ouput the registration statistics in-place. Set flag to disable output in-place. - --subtensor.register.verbose - Whether to ouput the registration statistics verbosely. - --subtensor.register.cuda.use_cuda, --cuda, --cuda.use_cuda - Set flag to use CUDA to register. - --subtensor.register.cuda.no_cuda, --no_cuda, --cuda.no_cuda - Set flag to not use CUDA for registration - --subtensor.register.cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...], --cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...] - Set the CUDA device id(s). Goes by the order of speed. (i.e. 0 is the fastest). - --subtensor.register.cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB, --cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB - Set the number of Threads Per Block for CUDA. - --logging.debug Turn on bittensor debugging information - --logging.trace Turn on bittensor trace level information - --logging.record_log Turns on logging to file. - --logging.logging_dir LOGGING.LOGGING_DIR - Logging default root directory. - --config CONFIG If set, defaults are overridden by passed file. - --strict If flagged, config will check that only exact arguemnts have been set. - ``` \ No newline at end of file diff --git a/neurons/text/prompting/miners/huggingface/neoxt/neuron.py b/neurons/text/prompting/miners/huggingface/neoxt/neuron.py deleted file mode 100644 index aa87c4a9a3..0000000000 --- a/neurons/text/prompting/miners/huggingface/neoxt/neuron.py +++ /dev/null @@ -1,69 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2023 Opentensor Foundation - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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 -from typing import List, Dict -from transformers import AutoTokenizer, AutoModelForCausalLM - - -class NeoxtMiner(bittensor.HuggingFaceMiner): - arg_prefix: str = "neoxt" - assistant_label: str = ":" - user_label: str = ":" - system_label: str = "" - - def load_tokenizer(self): - return AutoTokenizer.from_pretrained(self.config.neoxt.model_name) - - def load_model(self): - return AutoModelForCausalLM.from_pretrained( - self.config.neoxt.model_name, - torch_dtype=torch.float16, - low_cpu_mem_usage=True, - ) - - def forward(self, messages: List[Dict[str, str]]) -> str: - history = self.process_history(messages) - prompt = history + self.assistant_label - input_ids = self.tokenizer.encode(prompt, return_tensors="pt").to( - self.config.neoxt.device - ) - output = self.model.generate( - input_ids, - max_length=input_ids.shape[1] + self.config.neoxt.max_new_tokens, - temperature=self.config.neoxt.temperature, - do_sample=self.config.neoxt.do_sample, - pad_token_id=self.tokenizer.eos_token_id, - ) - generated_text = self.tokenizer.decode( - output[0][input_ids.shape[1] :], skip_special_tokens=True - ) - generation = generated_text.split("")[0].strip() - - bittensor.logging.debug( - "Message: " + str(messages).replace("<", "-").replace(">", "-") - ) - bittensor.logging.debug( - "Generation: " + str(generation).replace("<", "-").replace(">", "-") - ) - return generation - - -if __name__ == "__main__": - bittensor.utils.version_checking() - NeoxtMiner().run() diff --git a/neurons/text/prompting/miners/huggingface/neoxt/requirements.txt b/neurons/text/prompting/miners/huggingface/neoxt/requirements.txt deleted file mode 100644 index b33885a650..0000000000 --- a/neurons/text/prompting/miners/huggingface/neoxt/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -transformers>=4.27.4 -accelerate \ No newline at end of file diff --git a/neurons/text/prompting/miners/huggingface/oasst_pythia/README.md b/neurons/text/prompting/miners/huggingface/oasst_pythia/README.md deleted file mode 100644 index 6a011725d6..0000000000 --- a/neurons/text/prompting/miners/huggingface/oasst_pythia/README.md +++ /dev/null @@ -1,118 +0,0 @@ - -## OpenAssistant Pythia Miner -OpenAssistant's Pythia (12B) completion miner for bittensor's prompting network. - -# Example Usage -``` -python3 -m pip install -r neurons/text/prompting/miners/huggingface/oasst_pythia/requirements.txt -python3 neurons/text/prompting/miners/huggingface/oasst_pythia/neuron.py --oasst_pythia.OpenAssistant/oasst-sft-4-pythia-12b-epoch-3.5 -``` - -# Full Usage -``` -usage: neuron.py [-h] [--pythia12B.model_name PYTHIA12B.MODEL_NAME] [--pythia12B.load_in_8bit PYTHIA12B.LOAD_IN_8BIT] [--pythia12B.max_new_tokens PYTHIA12B.MAX_NEW_TOKENS] - [--pythia12B.temperature PYTHIA12B.TEMPERATURE] [--pythia12B.greedy_sampling] [--pythia12B.repetition-penalty PYTHIA12B.REPETITION_PENALTY] - [--pythia12B.top_p PYTHIA12B.TOP_P] [--pythia12B.top_k PYTHIA12B.TOP_K] [--netuid NETUID] [--neuron.name NEURON.NAME] - [--neuron.blocks_per_epoch NEURON.BLOCKS_PER_EPOCH] [--neuron.no_set_weights] [--neuron.max_batch_size NEURON.MAX_BATCH_SIZE] - [--neuron.max_sequence_len NEURON.MAX_SEQUENCE_LEN] [--neuron.blacklist.hotkeys [NEURON.BLACKLIST.HOTKEYS [NEURON.BLACKLIST.HOTKEYS ...]]] - [--neuron.blacklist.allow_non_registered] [--neuron.blacklist.default_stake NEURON.BLACKLIST.DEFAULT_STAKE] [--neuron.default_priority NEURON.DEFAULT_PRIORITY] - [--wallet.name WALLET.NAME] [--wallet.hotkey WALLET.HOTKEY] [--wallet.path WALLET.PATH] [--wallet._mock] [--wallet.reregister WALLET.REREGISTER] - [--axon.priority.max_workers AXON.PRIORITY.MAX_WORKERS] [--axon.priority.maxsize AXON.PRIORITY.MAXSIZE] [--axon.port AXON.PORT] [--axon.ip AXON.IP] - [--axon.external_port AXON.EXTERNAL_PORT] [--axon.external_ip AXON.EXTERNAL_IP] [--axon.max_workers AXON.MAX_WORKERS] - [--axon.maximum_concurrent_rpcs AXON.MAXIMUM_CONCURRENT_RPCS] [--subtensor.network SUBTENSOR.NETWORK] [--subtensor.chain_endpoint SUBTENSOR.CHAIN_ENDPOINT] - [--subtensor._mock] [--subtensor.register.num_processes SUBTENSOR.REGISTER.NUM_PROCESSES] [--subtensor.register.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL] - [--subtensor.register.no_output_in_place] [--subtensor.register.verbose] [--subtensor.register.cuda.use_cuda] [--subtensor.register.cuda.no_cuda] - [--subtensor.register.cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...]] [--subtensor.register.cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB] - [--logging.debug] [--logging.trace] [--logging.record_log] [--logging.logging_dir LOGGING.LOGGING_DIR] [--config CONFIG] [--strict] -optional arguments: - -h, --help show this help message and exit - --pythia12B.model_name PYTHIA12B.MODEL_NAME - Name/path of model to load - --pythia12B.load_in_8bit PYTHIA12B.LOAD_IN_8BIT - Load model in 8 bit precision - --pythia12B.max_new_tokens PYTHIA12B.MAX_NEW_TOKENS - Max tokens for model output. - --pythia12B.temperature PYTHIA12B.TEMPERATURE - Sampling temperature of model - --pythia12B.greedy_sampling - Whether to use greedy sampling or not (if not, uses multinomial sampling). - --pythia12B.repetition-penalty PYTHIA12B.REPETITION_PENALTY - Repetition penalty for greedy decoding. Between 1.0 and infinity. 1.0 means no penalty. Default: 1.0 - --pythia12B.top_p PYTHIA12B.TOP_P - Top-p (nucleus) sampling. Defaults to 1.0 (top-k sampling). Must be between 0.0 and 1.0. - --pythia12B.top_k PYTHIA12B.TOP_K - Top-k sampling. Defaults to 0 (no top-k sampling). Must be between 0 and 1000. - --netuid NETUID Subnet netuid - --neuron.name NEURON.NAME - Trials for this miner go in miner.root / (wallet_cold - wallet_hot) / miner.name - --neuron.blocks_per_epoch NEURON.BLOCKS_PER_EPOCH - Blocks until the miner sets weights on chain - --neuron.no_set_weights - If True, the model does not set weights. - --neuron.max_batch_size NEURON.MAX_BATCH_SIZE - The maximum batch size for forward requests. - --neuron.max_sequence_len NEURON.MAX_SEQUENCE_LEN - The maximum sequence length for forward requests. - --neuron.blacklist.hotkeys [NEURON.BLACKLIST.HOTKEYS [NEURON.BLACKLIST.HOTKEYS ...]] - To blacklist certain hotkeys - --neuron.blacklist.allow_non_registered - If True, the miner will allow non-registered hotkeys to mine. - --neuron.blacklist.default_stake NEURON.BLACKLIST.DEFAULT_STAKE - Set default stake for miners. - --neuron.default_priority NEURON.DEFAULT_PRIORITY - Set default priority for miners. - --wallet.name WALLET.NAME - The name of the wallet to unlock for running bittensor (name mock is reserved for mocking this wallet) - --wallet.hotkey WALLET.HOTKEY - The name of wallet's hotkey. - --wallet.path WALLET.PATH - The path to your bittensor wallets - --wallet._mock To turn on wallet mocking for testing purposes. - --wallet.reregister WALLET.REREGISTER - Whether to reregister the wallet if it is not already registered. - --axon.priority.max_workers AXON.PRIORITY.MAX_WORKERS - maximum number of threads in thread pool - --axon.priority.maxsize AXON.PRIORITY.MAXSIZE - maximum size of tasks in priority queue - --axon.port AXON.PORT - The local port this axon endpoint is bound to. i.e. 8091 - --axon.ip AXON.IP The local ip this axon binds to. ie. [::] - --axon.external_port AXON.EXTERNAL_PORT - The public port this axon broadcasts to the network. i.e. 8091 - --axon.external_ip AXON.EXTERNAL_IP - The external ip this axon broadcasts to the network to. ie. [::] - --axon.max_workers AXON.MAX_WORKERS - The maximum number connection handler threads working simultaneously on this endpoint. The grpc server distributes new worker threads to service requests up - to this number. - --axon.maximum_concurrent_rpcs AXON.MAXIMUM_CONCURRENT_RPCS - Maximum number of allowed active connections - --subtensor.network SUBTENSOR.NETWORK - The subtensor network flag. The likely choices are: -- finney (main network) -- local (local running network) -- mock (creates a mock connection (for - testing)) If this option is set it overloads subtensor.chain_endpoint with an entry point node from that network. - --subtensor.chain_endpoint SUBTENSOR.CHAIN_ENDPOINT - The subtensor endpoint flag. If set, overrides the --network flag. - --subtensor._mock To turn on subtensor mocking for testing purposes. - --subtensor.register.num_processes SUBTENSOR.REGISTER.NUM_PROCESSES, -n SUBTENSOR.REGISTER.NUM_PROCESSES - Number of processors to use for registration - --subtensor.register.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, --subtensor.register.cuda.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, --cuda.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, -u SUBTENSOR.REGISTER.UPDATE_INTERVAL - The number of nonces to process before checking for next block during registration - --subtensor.register.no_output_in_place, --no_output_in_place - Whether to not ouput the registration statistics in-place. Set flag to disable output in-place. - --subtensor.register.verbose - Whether to ouput the registration statistics verbosely. - --subtensor.register.cuda.use_cuda, --cuda, --cuda.use_cuda - Set flag to use CUDA to register. - --subtensor.register.cuda.no_cuda, --no_cuda, --cuda.no_cuda - Set flag to not use CUDA for registration - --subtensor.register.cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...], --cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...] - Set the CUDA device id(s). Goes by the order of speed. (i.e. 0 is the fastest). - --subtensor.register.cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB, --cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB - Set the number of Threads Per Block for CUDA. - --logging.debug Turn on bittensor debugging information - --logging.trace Turn on bittensor trace level information - --logging.record_log Turns on logging to file. - --logging.logging_dir LOGGING.LOGGING_DIR - Logging default root directory. - --config CONFIG If set, defaults are overridden by passed file. - --strict If flagged, config will check that only exact arguemnts have been set. -``` \ No newline at end of file diff --git a/neurons/text/prompting/miners/huggingface/oasst_pythia/neuron.py b/neurons/text/prompting/miners/huggingface/oasst_pythia/neuron.py deleted file mode 100644 index f8d959a8fe..0000000000 --- a/neurons/text/prompting/miners/huggingface/oasst_pythia/neuron.py +++ /dev/null @@ -1,100 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2023 Opentensor Foundation - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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 argparse -import bittensor -from typing import List, Dict -from transformers import AutoTokenizer, GPTNeoXForCausalLM -from transformers import StoppingCriteria, StoppingCriteriaList - - -class StopOnTokens(StoppingCriteria): - def __init__(self, stop_token_ids: List[int] = None): - self.stop_token_ids = stop_token_ids - - def __call__( - self, input_ids: torch.LongTensor, scores: torch.FloatTensor, **kwargs - ) -> bool: - for stop_id in self.stop_token_ids: - if input_ids[0][-1] == stop_id: - return True - return False - - -class OasstPythiaMiner(bittensor.HuggingFaceMiner): - arg_prefix = "oasst_pythia" - system_label = "<|system|>" - assistant_label = "<|assistant|>" - user_label = "<|prompter|>" - - def __init__(self): - super(OasstPythiaMiner, self).__init__() - self.stop = StopOnTokens( - self.tokenizer.convert_tokens_to_ids(["<|endoftext|>"]) - ) - - def load_tokenizer(self): - return AutoTokenizer.from_pretrained( - self.config.oasst_pythia.model_name, torch_dtype=torch.bfloat16 - ) - - def load_model(self): - bittensor.logging.info("Loading " + str(self.config.oasst_pythia.model_name)) - model = GPTNeoXForCausalLM.from_pretrained( - self.config.oasst_pythia.model_name, - device_map="auto", - low_cpu_mem_usage=True, - torch_dtype=torch.bfloat16, - ) - bittensor.logging.info("Model loaded!") - return model - - def forward(self, messages: List[Dict[str, str]]): - history = self.process_history(messages) - prompt = history + self.assistant_label - - inputs = self.tokenizer(prompt, return_tensors="pt") - inputs = inputs.to(self.model.device) - - gkw = { - **{ - "input_ids": inputs.input_ids, - "attention_mask": inputs.attention_mask, - "max_new_tokens": self.config.oasst_pythia.max_new_tokens, - "temperature": self.config.oasst_pythia.temperature, - "do_sample": self.config.oasst_pythia.do_sample, - "top_p": self.config.oasst_pythia.top_p, - "top_k": self.config.oasst_pythia.top_k, - "repetition_penalty": self.config.oasst_pythia.repetition_penalty, - "stopping_criteria": StoppingCriteriaList([self.stop]), - "pad_token_id": self.tokenizer.eos_token_id, - } - } - output = self.model.generate(**gkw) - generation = self.tokenizer.decode( - output[0][inputs.input_ids.shape[1] :], skip_special_tokens=True - ) - - bittensor.logging.debug("Message: " + str(messages)) - bittensor.logging.debug("Generation: " + str(generation)) - return generation - - -if __name__ == "__main__": - bittensor.utils.version_checking() - OasstPythiaMiner().run() diff --git a/neurons/text/prompting/miners/huggingface/open_llama/README.md b/neurons/text/prompting/miners/huggingface/open_llama/README.md deleted file mode 100644 index e81e38d306..0000000000 --- a/neurons/text/prompting/miners/huggingface/open_llama/README.md +++ /dev/null @@ -1,139 +0,0 @@ -## OpenLLaMA Miner -OpenLLaMA Language Model Serving with BitTensor -This code is for running the Open_llama model by OpenLM-Research through the BitTensor framework. - -# Overview - -## Contents - -- [Licence](#licence) -- [Model Download](#model-download) -- [Installing Dependencies](#installing-dependencies) -- [Starting Miner](#starting-miner) - -# Licence -Apache 2.0 (Open source and commercial purposes allowed) - -# Installing Dependencies - -``` -python3 -m pip install -r neurons/text/prompting/miners/huggingface/open_llama/requirements.txt -``` - -# Starting Miner -To start the miner, all you need to do is to specify the path to the model, or the name on Huggingface hub, and it will be downloaded. - -You can find different model checkpoints by searching Huggingface, or by looking at OpenLM-Research's Huggingface page https://huggingface.co/openlm-research - -``` -python3 neurons/text/prompting/miners/huggingface/open_llama/neuron.py --open_llama.model_name OPEN_LLAMA.MODEL_NAME_OR_PATH -``` - -# Full Usage -``` -usage: neuron.py [-h] [--open_llama.model_name OPEN_LLAMA.MODEL_NAME] [--open_llama.device OPEN_LLAMA.DEVICE] [--open_llama.max_new_tokens OPEN_LLAMA.MAX_NEW_TOKENS] - [--open_llama.temperature OPEN_LLAMA.TEMPERATURE] [--open_llama.do_sample] [--netuid NETUID] [--neuron.name NEURON.NAME] - [--neuron.blocks_per_epoch NEURON.BLOCKS_PER_EPOCH] [--neuron.no_set_weights] - [--neuron.max_batch_size NEURON.MAX_BATCH_SIZE] [--neuron.max_sequence_len NEURON.MAX_SEQUENCE_LEN] - [--neuron.blacklist.hotkeys [NEURON.BLACKLIST.HOTKEYS ...]] [--neuron.blacklist.allow_non_registered] - [--neuron.blacklist.default_stake NEURON.BLACKLIST.DEFAULT_STAKE] [--neuron.default_priority NEURON.DEFAULT_PRIORITY] - [--wallet.name WALLET.NAME] [--wallet.hotkey WALLET.HOTKEY] [--wallet.path WALLET.PATH] [--wallet._mock] - [--wallet.reregister WALLET.REREGISTER] [--axon.priority.max_workers AXON.PRIORITY.MAX_WORKERS] - [--axon.priority.maxsize AXON.PRIORITY.MAXSIZE] [--axon.port AXON.PORT] [--axon.ip AXON.IP] - [--axon.external_port AXON.EXTERNAL_PORT] [--axon.external_ip AXON.EXTERNAL_IP] [--axon.max_workers AXON.MAX_WORKERS] - [--axon.maximum_concurrent_rpcs AXON.MAXIMUM_CONCURRENT_RPCS] [--subtensor.network SUBTENSOR.NETWORK] - [--subtensor.chain_endpoint SUBTENSOR.CHAIN_ENDPOINT] [--subtensor._mock] - [--subtensor.register.num_processes SUBTENSOR.REGISTER.NUM_PROCESSES] - [--subtensor.register.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL] [--subtensor.register.no_output_in_place] - [--subtensor.register.verbose] [--subtensor.register.cuda.use_cuda] [--subtensor.register.cuda.no_cuda] - [--subtensor.register.cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...]] - [--subtensor.register.cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB] [--logging.debug] [--logging.trace] [--logging.record_log] - [--logging.logging_dir LOGGING.LOGGING_DIR] [--metagraph._mock] [--config CONFIG] [--strict] - -optional arguments: - -h, --help show this help message and exit - --neoxt.model_name NEOXT.MODEL_NAME - Name/path of model to load of model to load - --open_llama.device OPEN_LLAMA.DEVICE - Device to load model - --open_llama.max_new_tokens OPEN_LLAMA.MAX_NEW_TOKENS - Max tokens for model output. - --open_llama.temperature OPEN_LLAMA.TEMPERATURE - Sampling temperature of model - --open_llama.do_sample Whether to use sampling or not (if not, uses greedy decoding). - --netuid NETUID Subnet netuid - --neuron.name NEURON.NAME - Trials for this miner go in miner.root / (wallet_cold - wallet_hot) / miner.name - --neuron.blocks_per_epoch NEURON.BLOCKS_PER_EPOCH - Blocks until the miner sets weights on chain - --neuron.no_set_weights - If True, the model does not set weights. - --neuron.max_batch_size NEURON.MAX_BATCH_SIZE - The maximum batch size for forward requests. - --neuron.max_sequence_len NEURON.MAX_SEQUENCE_LEN - The maximum sequence length for forward requests. - --neuron.blacklist.hotkeys [NEURON.BLACKLIST.HOTKEYS ...] - To blacklist certain hotkeys - --neuron.blacklist.allow_non_registered - If True, the miner will allow non-registered hotkeys to mine. - --neuron.blacklist.default_stake NEURON.BLACKLIST.DEFAULT_STAKE - Set default stake for miners. - --neuron.default_priority NEURON.DEFAULT_PRIORITY - Set default priority for miners. - --wallet.name WALLET.NAME - The name of the wallet to unlock for running bittensor (name mock is reserved for mocking this wallet) - --wallet.hotkey WALLET.HOTKEY - The name of wallet's hotkey. - --wallet.path WALLET.PATH - The path to your bittensor wallets - --wallet._mock To turn on wallet mocking for testing purposes. - --wallet.reregister WALLET.REREGISTER - Whether to reregister the wallet if it is not already registered. - --axon.priority.max_workers AXON.PRIORITY.MAX_WORKERS - maximum number of threads in thread pool - --axon.priority.maxsize AXON.PRIORITY.MAXSIZE - maximum size of tasks in priority queue - --axon.port AXON.PORT - The local port this axon endpoint is bound to. i.e. 8091 - --axon.ip AXON.IP The local ip this axon binds to. ie. [::] - --axon.external_port AXON.EXTERNAL_PORT - The public port this axon broadcasts to the network. i.e. 8091 - --axon.external_ip AXON.EXTERNAL_IP - The external ip this axon broadcasts to the network to. ie. [::] - --axon.max_workers AXON.MAX_WORKERS - The maximum number connection handler threads working simultaneously on this endpoint. The grpc server distributes - new worker threads to service requests up to this number. - --axon.maximum_concurrent_rpcs AXON.MAXIMUM_CONCURRENT_RPCS - Maximum number of allowed active connections - --subtensor.network SUBTENSOR.NETWORK - The subtensor network flag. The likely choices are: -- finney (main network) -- local (local running network) -- - mock (creates a mock connection (for testing)) If this option is set it overloads subtensor.chain_endpoint with an - entry point node from that network. - --subtensor.chain_endpoint SUBTENSOR.CHAIN_ENDPOINT - The subtensor endpoint flag. If set, overrides the --network flag. - --subtensor._mock To turn on subtensor mocking for testing purposes. - --subtensor.register.num_processes SUBTENSOR.REGISTER.NUM_PROCESSES, -n SUBTENSOR.REGISTER.NUM_PROCESSES - Number of processors to use for registration - --subtensor.register.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, --subtensor.register.cuda.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, --cuda.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, -u SUBTENSOR.REGISTER.UPDATE_INTERVAL - The number of nonces to process before checking for next block during registration - --subtensor.register.no_output_in_place, --no_output_in_place - Whether to not ouput the registration statistics in-place. Set flag to disable output in-place. - --subtensor.register.verbose - Whether to ouput the registration statistics verbosely. - --subtensor.register.cuda.use_cuda, --cuda, --cuda.use_cuda - Set flag to use CUDA to register. - --subtensor.register.cuda.no_cuda, --no_cuda, --cuda.no_cuda - Set flag to not use CUDA for registration - --subtensor.register.cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...], --cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...] - Set the CUDA device id(s). Goes by the order of speed. (i.e. 0 is the fastest). - --subtensor.register.cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB, --cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB - Set the number of Threads Per Block for CUDA. - --logging.debug Turn on bittensor debugging information - --logging.trace Turn on bittensor trace level information - --logging.record_log Turns on logging to file. - --logging.logging_dir LOGGING.LOGGING_DIR - Logging default root directory. - --metagraph._mock To turn on metagraph mocking for testing purposes. - --config CONFIG If set, defaults are overridden by passed file. - --strict If flagged, config will check that only exact arguemnts have been set. -``` \ No newline at end of file diff --git a/neurons/text/prompting/miners/huggingface/open_llama/neuron.py b/neurons/text/prompting/miners/huggingface/open_llama/neuron.py deleted file mode 100644 index 705a2342c5..0000000000 --- a/neurons/text/prompting/miners/huggingface/open_llama/neuron.py +++ /dev/null @@ -1,71 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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 -from typing import List, Dict -from transformers import LlamaForCausalLM, LlamaTokenizer - - -class OpenLlamaMiner(bittensor.HuggingFaceMiner): - arg_prefix = "open_llama" - system_label = "\nSystem:" - assistant_label = "\nAssistant:" - user_label = "\nUser:" - - def load_tokenizer(self): - return LlamaTokenizer.from_pretrained( - self.config.open_llama.model_name, use_fast=False - ) - - def load_model(self): - return LlamaForCausalLM.from_pretrained( - self.config.open_llama.model_name, - torch_dtype=torch.float16, - low_cpu_mem_usage=True, - ) - - def forward(self, messages: List[Dict[str, str]]) -> str: - history = self.process_history(messages) - prompt = history + self.assistant_label - - input_ids = self.tokenizer.encode(prompt, return_tensors="pt").to( - self.config.open_llama.device - ) - output = self.model.generate( - input_ids, - max_length=input_ids.shape[1] + self.config.open_llama.max_new_tokens, - temperature=self.config.open_llama.temperature, - do_sample=self.config.open_llama.do_sample, - pad_token_id=self.tokenizer.eos_token_id, - ) - - generation = self.tokenizer.decode( - output[0][input_ids.shape[1] :], skip_special_tokens=True - ) - generation = generation.split("User:")[0].strip() - - # Logging input and generation if debugging is active - bittensor.logging.debug("Prompt: " + str(prompt)) - bittensor.logging.debug("Message: " + str(messages)) - bittensor.logging.debug("Generation: " + str(generation).replace("<", "-")) - return generation - - -if __name__ == "__main__": - bittensor.utils.version_checking() - OpenLlamaMiner().run() diff --git a/neurons/text/prompting/miners/huggingface/open_llama/requirements.txt b/neurons/text/prompting/miners/huggingface/open_llama/requirements.txt deleted file mode 100644 index 98637aee3d..0000000000 --- a/neurons/text/prompting/miners/huggingface/open_llama/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -git+https://github.com/huggingface/transformers -sentencepiece -fschat -tokenizers>=0.13.3 -accelerate diff --git a/neurons/text/prompting/miners/huggingface/pythia/README.md b/neurons/text/prompting/miners/huggingface/pythia/README.md deleted file mode 100644 index b5f5af5daa..0000000000 --- a/neurons/text/prompting/miners/huggingface/pythia/README.md +++ /dev/null @@ -1,136 +0,0 @@ -# Pythia Miner -togethercomputer/Pythia-7B Language Model Serving with BitTensor -This code is for running a language model powered by togethercomputer through the BitTensor framework. - -# Example Usage -``` -python3 -m pip install -r neurons/text/prompting/miners/huggingface/pythia/requirements.txt -python3 neurons/text/prompting/miners/huggingface/pythia/neuron.py --pythia.model_name togethercomputer/Pythia-Chat-Base-7B -``` - -# Full Usage -``` -usage: pythia_miner.py [-h] --pythia.model_name PYTHIA.MODEL_NAME [--pythia.device PYTHIA.DEVICE] [--pythia.max_new_tokens PYTHIA.MAX_NEW_TOKENS] - [--pythia.temperature PYTHIA.TEMPERATURE] [--pythia.do_sample] [--pythia.repetition_penalty PYTHIA.REPETITION_PENALTY] - [--pythia.do_prompt_injection] [--pythia.system_prompt PYTHIA.SYSTEM_PROMPT] - [--pythia.repetition-penalty PYTHIA.REPETITION_PENALTY] [--pythia.top_p PYTHIA.TOP_P] [--pythia.top_k PYTHIA.TOP_K] - [--pythia.load_in_8bit PYTHIA.LOAD_IN_8BIT] [--pythia.pad_tokens PYTHIA.PAD_TOKENS [PYTHIA.PAD_TOKENS ...]] - [--netuid NETUID] [--neuron.name NEURON.NAME] [--neuron.blocks_per_epoch NEURON.BLOCKS_PER_EPOCH] [--neuron.no_set_weights] - [--neuron.max_batch_size NEURON.MAX_BATCH_SIZE] [--neuron.max_sequence_len NEURON.MAX_SEQUENCE_LEN] - [--neuron.blacklist.hotkeys [NEURON.BLACKLIST.HOTKEYS [NEURON.BLACKLIST.HOTKEYS ...]]] - [--neuron.blacklist.allow_non_registered] [--neuron.blacklist.default_stake NEURON.BLACKLIST.DEFAULT_STAKE] - [--neuron.default_priority NEURON.DEFAULT_PRIORITY] [--wallet.name WALLET.NAME] [--wallet.hotkey WALLET.HOTKEY] - [--wallet.path WALLET.PATH] [--wallet._mock] [--wallet.reregister WALLET.REREGISTER] - [--axon.priority.max_workers AXON.PRIORITY.MAX_WORKERS] [--axon.priority.maxsize AXON.PRIORITY.MAXSIZE] - [--axon.port AXON.PORT] [--axon.ip AXON.IP] [--axon.external_port AXON.EXTERNAL_PORT] [--axon.external_ip AXON.EXTERNAL_IP] - [--axon.max_workers AXON.MAX_WORKERS] [--axon.maximum_concurrent_rpcs AXON.MAXIMUM_CONCURRENT_RPCS] - [--subtensor.network SUBTENSOR.NETWORK] [--subtensor.chain_endpoint SUBTENSOR.CHAIN_ENDPOINT] [--subtensor._mock] - [--subtensor.register.num_processes SUBTENSOR.REGISTER.NUM_PROCESSES] - [--subtensor.register.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL] [--subtensor.register.no_output_in_place] - [--subtensor.register.verbose] [--subtensor.register.cuda.use_cuda] [--subtensor.register.cuda.no_cuda] - [--subtensor.register.cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...]] - [--subtensor.register.cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB] [--logging.debug] [--logging.trace] [--logging.record_log] - [--logging.logging_dir LOGGING.LOGGING_DIR] [--config CONFIG] [--strict] - -optional arguments: - -h, --help show this help message and exit - --pythia.model_name PYTHIA.MODEL_NAME - Name or path of model to load - --pythia.device PYTHIA.DEVICE - Device to load model - --pythia.max_new_tokens PYTHIA.MAX_NEW_TOKENS - Max tokens for model output. - --pythia.temperature PYTHIA.TEMPERATURE - Sampling temperature of model - --pythia.do_sample Whether to use multinomial sampling. - --pythia.repetition_penalty PYTHIA.REPETITION_PENALTY - Repetition penalty for model - --pythia.do_prompt_injection - Whether to use a custom "system" prompt instead of the one sent by bittensor. - --pythia.system_prompt PYTHIA.SYSTEM_PROMPT - What prompt to replace the system prompt with - --pythia.repetition-penalty PYTHIA.REPETITION_PENALTY - Repetition penalty for greedy decoding. Between 1.0 and infinity. 1.0 means no penalty. Default: 1.0 - --pythia.top_p PYTHIA.TOP_P - Top-p (nucleus) sampling. Defaults to 1.0 (top-k sampling). Must be between 0.0 and 1.0. - --pythia.top_k PYTHIA.TOP_K - Top-k sampling. Defaults to 0 (no top-k sampling). Must be between 0 and 1000. - --pythia.load_in_8bit PYTHIA.LOAD_IN_8BIT - Load model in 8 bit precision - --pythia.pad_tokens PYTHIA.PAD_TOKENS [PYTHIA.PAD_TOKENS ...] - A list of integers separated by spaces for the pad_tokens. - --netuid NETUID Subnet netuid - --neuron.name NEURON.NAME - Trials for this miner go in miner.root / (wallet_cold - wallet_hot) / miner.name - --neuron.blocks_per_epoch NEURON.BLOCKS_PER_EPOCH - Blocks until the miner sets weights on chain - --neuron.no_set_weights - If True, the model does not set weights. - --neuron.max_batch_size NEURON.MAX_BATCH_SIZE - The maximum batch size for forward requests. - --neuron.max_sequence_len NEURON.MAX_SEQUENCE_LEN - The maximum sequence length for forward requests. - --neuron.blacklist.hotkeys [NEURON.BLACKLIST.HOTKEYS [NEURON.BLACKLIST.HOTKEYS ...]] - To blacklist certain hotkeys - --neuron.blacklist.allow_non_registered - If True, the miner will allow non-registered hotkeys to mine. - --neuron.blacklist.default_stake NEURON.BLACKLIST.DEFAULT_STAKE - Set default stake for miners. - --neuron.default_priority NEURON.DEFAULT_PRIORITY - Set default priority for miners. - --wallet.name WALLET.NAME - The name of the wallet to unlock for running bittensor (name mock is reserved for mocking this wallet) - --wallet.hotkey WALLET.HOTKEY - The name of wallet's hotkey. - --wallet.path WALLET.PATH - The path to your bittensor wallets - --wallet._mock To turn on wallet mocking for testing purposes. - --wallet.reregister WALLET.REREGISTER - Whether to reregister the wallet if it is not already registered. - --axon.priority.max_workers AXON.PRIORITY.MAX_WORKERS - maximum number of threads in thread pool - --axon.priority.maxsize AXON.PRIORITY.MAXSIZE - maximum size of tasks in priority queue - --axon.port AXON.PORT - The local port this axon endpoint is bound to. i.e. 8091 - --axon.ip AXON.IP The local ip this axon binds to. ie. [::] - --axon.external_port AXON.EXTERNAL_PORT - The public port this axon broadcasts to the network. i.e. 8091 - --axon.external_ip AXON.EXTERNAL_IP - The external ip this axon broadcasts to the network to. ie. [::] - --axon.max_workers AXON.MAX_WORKERS - The maximum number connection handler threads working simultaneously on this endpoint. The grpc server distributes new - worker threads to service requests up to this number. - --axon.maximum_concurrent_rpcs AXON.MAXIMUM_CONCURRENT_RPCS - Maximum number of allowed active connections - --subtensor.network SUBTENSOR.NETWORK - The subtensor network flag. The likely choices are: -- finney (main network) -- local (local running network) -- mock - (creates a mock connection (for testing)) If this option is set it overloads subtensor.chain_endpoint with an entry point - node from that network. - --subtensor.chain_endpoint SUBTENSOR.CHAIN_ENDPOINT - The subtensor endpoint flag. If set, overrides the --network flag. - --subtensor._mock To turn on subtensor mocking for testing purposes. - --subtensor.register.num_processes SUBTENSOR.REGISTER.NUM_PROCESSES, -n SUBTENSOR.REGISTER.NUM_PROCESSES - Number of processors to use for registration - --subtensor.register.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, --subtensor.register.cuda.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, --cuda.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, -u SUBTENSOR.REGISTER.UPDATE_INTERVAL - The number of nonces to process before checking for next block during registration - --subtensor.register.no_output_in_place, --no_output_in_place - Whether to not ouput the registration statistics in-place. Set flag to disable output in-place. - --subtensor.register.verbose - Whether to ouput the registration statistics verbosely. - --subtensor.register.cuda.use_cuda, --cuda, --cuda.use_cuda - Set flag to use CUDA to register. - --subtensor.register.cuda.no_cuda, --no_cuda, --cuda.no_cuda - Set flag to not use CUDA for registration - --subtensor.register.cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...], --cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...] - Set the CUDA device id(s). Goes by the order of speed. (i.e. 0 is the fastest). - --subtensor.register.cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB, --cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB - Set the number of Threads Per Block for CUDA. - --logging.debug Turn on bittensor debugging information - --logging.trace Turn on bittensor trace level information - --logging.record_log Turns on logging to file. - --logging.logging_dir LOGGING.LOGGING_DIR - Logging default root directory. - --config CONFIG If set, defaults are overridden by passed file. - --strict If flagged, config will check that only exact arguemnts have been set. - ``` \ No newline at end of file diff --git a/neurons/text/prompting/miners/huggingface/pythia/neuron.py b/neurons/text/prompting/miners/huggingface/pythia/neuron.py deleted file mode 100644 index 72143e7184..0000000000 --- a/neurons/text/prompting/miners/huggingface/pythia/neuron.py +++ /dev/null @@ -1,69 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2023 Yuma Rao - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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 -from typing import List, Dict -from transformers import AutoTokenizer, AutoModelForCausalLM - - -class PythiaMiner(bittensor.HuggingFaceMiner): - arg_prefix: str = "pythia" - assistant_label: str = ":" - user_label: str = ":" - system_label: str = "" - - def load_tokenizer(self): - return AutoTokenizer.from_pretrained(self.config.pythia.model_name) - - def load_model(self): - return AutoModelForCausalLM.from_pretrained( - self.config.pythia.model_name, - torch_dtype=torch.float16, - low_cpu_mem_usage=True, - ) - - def forward(self, messages: List[Dict[str, str]]) -> str: - history = self.process_history(messages) - prompt = history + self.assistant_label - input_ids = self.tokenizer.encode(prompt, return_tensors="pt").to( - self.config.pythia.device - ) - output = self.model.generate( - input_ids, - max_length=input_ids.shape[1] + self.config.pythia.max_new_tokens, - temperature=self.config.pythia.temperature, - do_sample=self.config.pythia.do_sample, - pad_token_id=self.tokenizer.eos_token_id, - ) - generated_text = self.tokenizer.decode( - output[0][input_ids.shape[1] :], skip_special_tokens=True - ) - generation = generated_text.split("")[0].strip() - - bittensor.logging.debug( - "Message: " + str(messages).replace("<", "-").replace(">", "-") - ) - bittensor.logging.debug( - "Generation: " + str(generation).replace("<", "-").replace(">", "-") - ) - return generation - - -if __name__ == "__main__": - bittensor.utils.version_checking() - PythiaMiner().run() diff --git a/neurons/text/prompting/miners/huggingface/pythia/requirements.txt b/neurons/text/prompting/miners/huggingface/pythia/requirements.txt deleted file mode 100644 index b33885a650..0000000000 --- a/neurons/text/prompting/miners/huggingface/pythia/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -transformers>=4.27.4 -accelerate \ No newline at end of file diff --git a/neurons/text/prompting/miners/huggingface/raven/README.md b/neurons/text/prompting/miners/huggingface/raven/README.md deleted file mode 100644 index d931a450cb..0000000000 --- a/neurons/text/prompting/miners/huggingface/raven/README.md +++ /dev/null @@ -1,143 +0,0 @@ -# Raven RWKV Miner -BlinkDL/Raven-RWKV-7B Language Model Serving with BitTensor -This code is for running a language model powered by BlinkDL through the BitTensor framework. - -## Setup -Go to the huggingface repo for more information: [rwkv-4-raven](https://huggingface.co/BlinkDL/rwkv-4-raven) - -NOTE: You need to pass the path to the tokenizer.json from the command line. -- Find it [here](https://huggingface.co/spaces/BlinkDL/Raven-RWKV-7B/resolve/main/20B_tokenizer.json) - -NOTE: You will want to browse and see what Raven model you wish to load [here](https://huggingface.co/BlinkDL/rwkv-4-raven/tree/main) -e.g. `RWKV-4-Raven-7B-v11-Eng99%25-Other1%25-20230427-ctx8192` for Engligh 99% and Other languages 1%= -e.g. `RWKV-4-Raven-7B-v11-Eng49%-Chn49%-Jpn1%-Other1%-20230430-ctx8192` for 49% English, 49% Chinese, 1% Japanese - -These percentages refer to the amount of training data from that particular language. - -# Usage -``` -wget https://huggingface.co/spaces/BlinkDL/Raven-RWKV-7B/resolve/main/20B_tokenizer.json -python3 -m pip install -r neurons/text/prompting/miners/huggingface/raven-rwkv/requirements.txt -python3 neurons/text/prompting/miners/huggingface/raven-rwkv/neuron.py --raven.tokenizer_path /home/jason/bittensor/20B_tokenizer.json \ - --raven.model_name RWKV-4-Raven-7B-v11x-Eng99%-Other1%-20230429-ctx8192 \ - --raven.repetition-penalty 0.2 --raven.top_p 0.0 --raven.temperature 1.0 -``` - -# Full Usage -``` -usage: neuron.py [-h] [--raven.model_name RAVEN.MODEL_NAME] [--raven.repo_id RAVEN.REPO_ID] [--raven.tokenizer_path RAVEN.TOKENIZER_PATH] [--raven.device RAVEN.DEVICE] [--raven.ctx_limit RAVEN.CTX_LIMIT] [--raven.max_new_tokens RAVEN.MAX_NEW_TOKENS] - [--raven.temperature RAVEN.TEMPERATURE] [--raven.top_p RAVEN.TOP_P] [--raven.do_prompt_injection] [--raven.system_prompt RAVEN.SYSTEM_PROMPT] [--raven.jit_on] [--raven.cuda_on] [--raven.strategy RAVEN.STRATEGY] - [--raven.pad_tokens RAVEN.PAD_TOKENS [RAVEN.PAD_TOKENS ...]] [--raven.repetition_penalty RAVEN.REPETITION_PENALTY] [--netuid NETUID] [--neuron.name NEURON.NAME] [--neuron.blocks_per_epoch NEURON.BLOCKS_PER_EPOCH] [--neuron.no_set_weights] - [--neuron.max_batch_size NEURON.MAX_BATCH_SIZE] [--neuron.max_sequence_len NEURON.MAX_SEQUENCE_LEN] [--neuron.blacklist.hotkeys [NEURON.BLACKLIST.HOTKEYS [NEURON.BLACKLIST.HOTKEYS ...]]] [--neuron.blacklist.allow_non_registered] - [--neuron.blacklist.default_stake NEURON.BLACKLIST.DEFAULT_STAKE] [--neuron.blacklist.vpermit_required] [--neuron.default_priority NEURON.DEFAULT_PRIORITY] [--wallet.name WALLET.NAME] [--wallet.hotkey WALLET.HOTKEY] [--wallet.path WALLET.PATH] [--wallet._mock] - [--wallet.reregister WALLET.REREGISTER] [--axon.priority.max_workers AXON.PRIORITY.MAX_WORKERS] [--axon.priority.maxsize AXON.PRIORITY.MAXSIZE] [--axon.port AXON.PORT] [--axon.ip AXON.IP] [--axon.external_port AXON.EXTERNAL_PORT] - [--axon.external_ip AXON.EXTERNAL_IP] [--axon.max_workers AXON.MAX_WORKERS] [--axon.maximum_concurrent_rpcs AXON.MAXIMUM_CONCURRENT_RPCS] [--subtensor.network SUBTENSOR.NETWORK] [--subtensor.chain_endpoint SUBTENSOR.CHAIN_ENDPOINT] [--subtensor._mock] - [--subtensor.register.num_processes SUBTENSOR.REGISTER.NUM_PROCESSES] [--subtensor.register.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL] [--subtensor.register.no_output_in_place] [--subtensor.register.verbose] [--subtensor.register.cuda.use_cuda] - [--subtensor.register.cuda.no_cuda] [--subtensor.register.cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...]] [--subtensor.register.cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB] [--logging.debug] [--logging.trace] [--logging.record_log] - [--logging.logging_dir LOGGING.LOGGING_DIR] [--config CONFIG] [--strict] - -optional arguments: - -h, --help show this help message and exit - --raven.model_name RAVEN.MODEL_NAME - Name/path of model to load - --raven.repo_id RAVEN.REPO_ID - Repo id of model to load - --raven.tokenizer_path RAVEN.TOKENIZER_PATH - Path to tokenizer json file - --raven.device RAVEN.DEVICE - Device to load model - --raven.ctx_limit RAVEN.CTX_LIMIT - Max context length for model input. - --raven.max_new_tokens RAVEN.MAX_NEW_TOKENS - Max tokens for model output. - --raven.temperature RAVEN.TEMPERATURE - Sampling temperature of model - --raven.top_p RAVEN.TOP_P - Top p sampling of model - --raven.do_prompt_injection - Whether to use a custom "system" prompt instead of the one sent by bittensor. - --raven.system_prompt RAVEN.SYSTEM_PROMPT - What prompt to replace the system prompt with - --raven.jit_on Whether to use Just-In-Time complication (JIT) - --raven.cuda_on Whether to use CUDA kernel for seq mode (much faster). [Requires CUDA_HOME env_variable to be set] - --raven.strategy RAVEN.STRATEGY - Strategy to use for RWKV model - --raven.pad_tokens RAVEN.PAD_TOKENS [RAVEN.PAD_TOKENS ...] - A list of integers separated by spaces for the pad_tokens. - --raven.repetition_penalty RAVEN.REPETITION_PENALTY - Repetition penalty for RWKV model - --netuid NETUID Subnet netuid - --neuron.name NEURON.NAME - Trials for this miner go in miner.root / (wallet_cold - wallet_hot) / miner.name - --neuron.blocks_per_epoch NEURON.BLOCKS_PER_EPOCH - Blocks until the miner sets weights on chain - --neuron.no_set_weights - If True, the model does not set weights. - --neuron.max_batch_size NEURON.MAX_BATCH_SIZE - The maximum batch size for forward requests. - --neuron.max_sequence_len NEURON.MAX_SEQUENCE_LEN - The maximum sequence length for forward requests. - --neuron.blacklist.hotkeys [NEURON.BLACKLIST.HOTKEYS [NEURON.BLACKLIST.HOTKEYS ...]] - To blacklist certain hotkeys - --neuron.blacklist.allow_non_registered - If True, this miner will allow non-registered hotkeys to query it. - --neuron.blacklist.default_stake NEURON.BLACKLIST.DEFAULT_STAKE - Set default stake for miners. - --neuron.blacklist.vpermit_required - Require vpermit to query this miner. - --neuron.default_priority NEURON.DEFAULT_PRIORITY - Set default priority for miners. - --wallet.name WALLET.NAME - The name of the wallet to unlock for running bittensor (name mock is reserved for mocking this wallet) - --wallet.hotkey WALLET.HOTKEY - The name of wallet's hotkey. - --wallet.path WALLET.PATH - The path to your bittensor wallets - --wallet._mock To turn on wallet mocking for testing purposes. - --wallet.reregister WALLET.REREGISTER - Whether to reregister the wallet if it is not already registered. - --axon.priority.max_workers AXON.PRIORITY.MAX_WORKERS - maximum number of threads in thread pool - --axon.priority.maxsize AXON.PRIORITY.MAXSIZE - maximum size of tasks in priority queue - --axon.port AXON.PORT - The local port this axon endpoint is bound to. i.e. 8091 - --axon.ip AXON.IP The local ip this axon binds to. ie. [::] - --axon.external_port AXON.EXTERNAL_PORT - The public port this axon broadcasts to the network. i.e. 8091 - --axon.external_ip AXON.EXTERNAL_IP - The external ip this axon broadcasts to the network to. ie. [::] - --axon.max_workers AXON.MAX_WORKERS - The maximum number connection handler threads working simultaneously on this endpoint. The grpc server distributes new worker threads to service requests up to this number. - --axon.maximum_concurrent_rpcs AXON.MAXIMUM_CONCURRENT_RPCS - Maximum number of allowed active connections - --subtensor.network SUBTENSOR.NETWORK - The subtensor network flag. The likely choices are: -- finney (main network) -- local (local running network) -- mock (creates a mock connection (for testing)) If this option is set it overloads subtensor.chain_endpoint with an entry point node from that - network. - --subtensor.chain_endpoint SUBTENSOR.CHAIN_ENDPOINT - The subtensor endpoint flag. If set, overrides the --network flag. - --subtensor._mock To turn on subtensor mocking for testing purposes. - --subtensor.register.num_processes SUBTENSOR.REGISTER.NUM_PROCESSES, -n SUBTENSOR.REGISTER.NUM_PROCESSES - Number of processors to use for registration - --subtensor.register.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, --subtensor.register.cuda.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, --cuda.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, -u SUBTENSOR.REGISTER.UPDATE_INTERVAL - The number of nonces to process before checking for next block during registration - --subtensor.register.no_output_in_place, --no_output_in_place - Whether to not ouput the registration statistics in-place. Set flag to disable output in-place. - --subtensor.register.verbose - Whether to ouput the registration statistics verbosely. - --subtensor.register.cuda.use_cuda, --cuda, --cuda.use_cuda - Set flag to use CUDA to register. - --subtensor.register.cuda.no_cuda, --no_cuda, --cuda.no_cuda - Set flag to not use CUDA for registration - --subtensor.register.cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...], --cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...] - Set the CUDA device id(s). Goes by the order of speed. (i.e. 0 is the fastest). - --subtensor.register.cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB, --cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB - Set the number of Threads Per Block for CUDA. - --logging.debug Turn on bittensor debugging information - --logging.trace Turn on bittensor trace level information - --logging.record_log Turns on logging to file. - --logging.logging_dir LOGGING.LOGGING_DIR - Logging default root directory. - --config CONFIG If set, defaults are overridden by passed file. - --strict If flagged, config will check that only exact arguemnts have been set. - ``` \ No newline at end of file diff --git a/neurons/text/prompting/miners/huggingface/raven/neuron.py b/neurons/text/prompting/miners/huggingface/raven/neuron.py deleted file mode 100644 index a7ff469cbf..0000000000 --- a/neurons/text/prompting/miners/huggingface/raven/neuron.py +++ /dev/null @@ -1,151 +0,0 @@ -import os -import argparse -import bittensor -from typing import List, Dict -from huggingface_hub import hf_hub_download -from rwkv.model import RWKV -from rwkv.utils import PIPELINE - - -class RavenMiner(bittensor.HuggingFaceMiner): - arg_prefix = "raven" - system_label = "" - assistant_label = "Alice:" - user_label = "Bob:" - - @classmethod - def add_args(cls, parser: argparse.ArgumentParser): - super(RavenMiner, cls).add_args(parser) - parser.add_argument( - "--raven.repo_id", - type=str, - default="BlinkDL/rwkv-4-raven", - help="Repo id of model to load", - ) - parser.add_argument( - "--raven.tokenizer_path", - type=str, - required=True, - help="Path to tokenizer json file", - ) - parser.add_argument( - "--raven.ctx_limit", - type=int, - help="Max context length for model input.", - default=1536, - ) - parser.add_argument( - "--raven.jit_on", - action="store_true", - default=False, - help="Whether to use Just-In-Time complication (JIT)", - ) - parser.add_argument( - "--raven.cuda_on", - action="store_true", - default=False, - help="Whether to use CUDA kernel for seq mode (much faster). [Requires CUDA_HOME env_variable to be set]", - ) - parser.add_argument( - "--raven.strategy", - type=str, - default="cuda fp16i8 *8 -> cuda fp16", - help="Strategy to use for RWKV model", - ) - - def __init__(self): - super(RavenMiner, self).__init__() - - def load_model(self): - model_path = hf_hub_download( - repo_id=self.config.raven.repo_id, - filename=f"{self.config.raven.model_name}.pth", - ) - model = RWKV(model=model_path, strategy=self.config.raven.strategy) - return PIPELINE(model, self.config.raven.tokenizer_path) - - def load_tokenizer(self): - pass - - os.environ["RWKV_JIT_ON"] = "1" if self.config.raven.jit_on else "0" - os.environ["RWKV_CUDA_ON"] = "1" if self.config.raven.cuda_on else "0" - - def forward(self, messages: List[Dict[str, str]]) -> str: - history = self.process_history(messages) - - out_tokens = [] - out_last = 0 - generation = "" - occurrence = {} - state = None - for i in range(self.config.raven.max_new_tokens): - tokens = ( - self.config.raven.pad_tokens + self.model.encode(history) - if i == 0 - else [token] - ) - - out, state = self.model.model.forward(tokens, state) - for n in occurrence: - out[n] -= ( - self.config.raven.repetition_penalty - + occurrence[n] * self.config.raven.repetition_penalty - ) - - token = self.model.sample_logits( - out, - temperature=self.config.raven.temperature, - top_p=self.config.raven.top_p, - ) - if token == 0: - break # exit when 'endoftext' - - out_tokens += [token] - occurrence[token] = 1 + (occurrence[token] if token in occurrence else 0) - - tmp = self.model.decode(out_tokens[out_last:]) - if ("\ufffd" not in tmp) and (not tmp.endswith("\n")): - generation += tmp - out_last = i + 1 - - if "\n\n" in tmp: # exit when '\n\n' - generation += tmp - generation = generation.strip() - break - - bittensor.logging.debug("Message: " + str(messages)) - bittensor.logging.debug("Generation: " + str(generation)) - return generation - - -if __name__ == "__main__": - bittensor.utils.version_checking() - RavenMiner().run() - - -def test_miner(model): - prompt = """ - You are George Carlin. - George Carlin is a comedian known for his witty, cutting, poignant observational comedy. - He is also known for his social commentary, philosophy, and cutting remarks on religion. - Write a joke about the following topic: - """ - - message = "who am I, really?" - - if prompt is not None: - roles = ["system", "user"] - messages = [prompt, message] - else: - roles = ["user"] - messages = [message] - - messages = [ - {"role": role, "content": message} for role, message in zip(roles, messages) - ] - - return model.forward(messages) - - -miner = RavenMiner() -print(test_miner(miner)) diff --git a/neurons/text/prompting/miners/huggingface/raven/requirements.txt b/neurons/text/prompting/miners/huggingface/raven/requirements.txt deleted file mode 100644 index 2573350bcc..0000000000 --- a/neurons/text/prompting/miners/huggingface/raven/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -rwkv -huggingface_hub \ No newline at end of file diff --git a/neurons/text/prompting/miners/huggingface/robertmyers/README.md b/neurons/text/prompting/miners/huggingface/robertmyers/README.md deleted file mode 100644 index 965e435fc4..0000000000 --- a/neurons/text/prompting/miners/huggingface/robertmyers/README.md +++ /dev/null @@ -1,140 +0,0 @@ - -## RoberMyers Miner -Robert myers completion miner for bittensor's prompting network. - -# Example Usage -``` -python3 -m pip install -r neurons/text/prompting/miners/huggingface/robertmyers/requirements.txt -python3 neurons/text/prompting/miners/huggingface/robertmyers/neuron.py --robertmyers.model_name robertmyers/bpt-sft -``` - -# Full Usage -``` -usage: robertmyers_miner.py [-h] --robertmyers.model_name ROBERTMYERS.MODEL_NAME [--robertmyers.device ROBERTMYERS.DEVICE] - [--robertmyers.max_new_tokens ROBERTMYERS.MAX_NEW_TOKENS] [--robertmyers.temperature ROBERTMYERS.TEMPERATURE] - [--robertmyers.do_sample] [--robertmyers.repetition_penalty ROBERTMYERS.REPETITION_PENALTY] - [--robertmyers.do_prompt_injection] [--robertmyers.system_prompt ROBERTMYERS.SYSTEM_PROMPT] - [--robertmyers.repetition-penalty ROBERTMYERS.REPETITION_PENALTY] [--robertmyers.top_p ROBERTMYERS.TOP_P] - [--robertmyers.top_k ROBERTMYERS.TOP_K] [--robertmyers.load_in_8bit ROBERTMYERS.LOAD_IN_8BIT] - [--robertmyers.pad_tokens ROBERTMYERS.PAD_TOKENS [ROBERTMYERS.PAD_TOKENS ...]] [--netuid NETUID] - [--neuron.name NEURON.NAME] [--neuron.blocks_per_epoch NEURON.BLOCKS_PER_EPOCH] [--neuron.no_set_weights] - [--neuron.max_batch_size NEURON.MAX_BATCH_SIZE] [--neuron.max_sequence_len NEURON.MAX_SEQUENCE_LEN] - [--neuron.blacklist.hotkeys [NEURON.BLACKLIST.HOTKEYS [NEURON.BLACKLIST.HOTKEYS ...]]] - [--neuron.blacklist.allow_non_registered] [--neuron.blacklist.default_stake NEURON.BLACKLIST.DEFAULT_STAKE] - [--neuron.default_priority NEURON.DEFAULT_PRIORITY] [--wallet.name WALLET.NAME] [--wallet.hotkey WALLET.HOTKEY] - [--wallet.path WALLET.PATH] [--wallet._mock] [--wallet.reregister WALLET.REREGISTER] - [--axon.priority.max_workers AXON.PRIORITY.MAX_WORKERS] [--axon.priority.maxsize AXON.PRIORITY.MAXSIZE] - [--axon.port AXON.PORT] [--axon.ip AXON.IP] [--axon.external_port AXON.EXTERNAL_PORT] - [--axon.external_ip AXON.EXTERNAL_IP] [--axon.max_workers AXON.MAX_WORKERS] - [--axon.maximum_concurrent_rpcs AXON.MAXIMUM_CONCURRENT_RPCS] [--subtensor.network SUBTENSOR.NETWORK] - [--subtensor.chain_endpoint SUBTENSOR.CHAIN_ENDPOINT] [--subtensor._mock] - [--subtensor.register.num_processes SUBTENSOR.REGISTER.NUM_PROCESSES] - [--subtensor.register.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL] [--subtensor.register.no_output_in_place] - [--subtensor.register.verbose] [--subtensor.register.cuda.use_cuda] [--subtensor.register.cuda.no_cuda] - [--subtensor.register.cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...]] - [--subtensor.register.cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB] [--logging.debug] [--logging.trace] [--logging.record_log] - [--logging.logging_dir LOGGING.LOGGING_DIR] [--config CONFIG] [--strict] - -optional arguments: - -h, --help show this help message and exit - --robertmyers.model_name ROBERTMYERS.MODEL_NAME - Name or path of model to load - --robertmyers.device ROBERTMYERS.DEVICE - Device to load model - --robertmyers.max_new_tokens ROBERTMYERS.MAX_NEW_TOKENS - Max tokens for model output. - --robertmyers.temperature ROBERTMYERS.TEMPERATURE - Sampling temperature of model - --robertmyers.do_sample - Whether to use multinomial sampling. - --robertmyers.repetition_penalty ROBERTMYERS.REPETITION_PENALTY - Repetition penalty for model - --robertmyers.do_prompt_injection - Whether to use a custom "system" prompt instead of the one sent by bittensor. - --robertmyers.system_prompt ROBERTMYERS.SYSTEM_PROMPT - What prompt to replace the system prompt with - --robertmyers.repetition-penalty ROBERTMYERS.REPETITION_PENALTY - Repetition penalty for greedy decoding. Between 1.0 and infinity. 1.0 means no penalty. Default: 1.0 - --robertmyers.top_p ROBERTMYERS.TOP_P - Top-p (nucleus) sampling. Defaults to 1.0 (top-k sampling). Must be between 0.0 and 1.0. - --robertmyers.top_k ROBERTMYERS.TOP_K - Top-k sampling. Defaults to 0 (no top-k sampling). Must be between 0 and 1000. - --robertmyers.load_in_8bit ROBERTMYERS.LOAD_IN_8BIT - Load model in 8 bit precision - --robertmyers.pad_tokens ROBERTMYERS.PAD_TOKENS [ROBERTMYERS.PAD_TOKENS ...] - A list of integers separated by spaces for the pad_tokens. - --netuid NETUID Subnet netuid - --neuron.name NEURON.NAME - Trials for this miner go in miner.root / (wallet_cold - wallet_hot) / miner.name - --neuron.blocks_per_epoch NEURON.BLOCKS_PER_EPOCH - Blocks until the miner sets weights on chain - --neuron.no_set_weights - If True, the model does not set weights. - --neuron.max_batch_size NEURON.MAX_BATCH_SIZE - The maximum batch size for forward requests. - --neuron.max_sequence_len NEURON.MAX_SEQUENCE_LEN - The maximum sequence length for forward requests. - --neuron.blacklist.hotkeys [NEURON.BLACKLIST.HOTKEYS [NEURON.BLACKLIST.HOTKEYS ...]] - To blacklist certain hotkeys - --neuron.blacklist.allow_non_registered - If True, the miner will allow non-registered hotkeys to mine. - --neuron.blacklist.default_stake NEURON.BLACKLIST.DEFAULT_STAKE - Set default stake for miners. - --neuron.default_priority NEURON.DEFAULT_PRIORITY - Set default priority for miners. - --wallet.name WALLET.NAME - The name of the wallet to unlock for running bittensor (name mock is reserved for mocking this wallet) - --wallet.hotkey WALLET.HOTKEY - The name of wallet's hotkey. - --wallet.path WALLET.PATH - The path to your bittensor wallets - --wallet._mock To turn on wallet mocking for testing purposes. - --wallet.reregister WALLET.REREGISTER - Whether to reregister the wallet if it is not already registered. - --axon.priority.max_workers AXON.PRIORITY.MAX_WORKERS - maximum number of threads in thread pool - --axon.priority.maxsize AXON.PRIORITY.MAXSIZE - maximum size of tasks in priority queue - --axon.port AXON.PORT - The local port this axon endpoint is bound to. i.e. 8091 - --axon.ip AXON.IP The local ip this axon binds to. ie. [::] - --axon.external_port AXON.EXTERNAL_PORT - The public port this axon broadcasts to the network. i.e. 8091 - --axon.external_ip AXON.EXTERNAL_IP - The external ip this axon broadcasts to the network to. ie. [::] - --axon.max_workers AXON.MAX_WORKERS - The maximum number connection handler threads working simultaneously on this endpoint. The grpc server distributes new - worker threads to service requests up to this number. - --axon.maximum_concurrent_rpcs AXON.MAXIMUM_CONCURRENT_RPCS - Maximum number of allowed active connections - --subtensor.network SUBTENSOR.NETWORK - The subtensor network flag. The likely choices are: -- finney (main network) -- local (local running network) -- mock - (creates a mock connection (for testing)) If this option is set it overloads subtensor.chain_endpoint with an entry point - node from that network. - --subtensor.chain_endpoint SUBTENSOR.CHAIN_ENDPOINT - The subtensor endpoint flag. If set, overrides the --network flag. - --subtensor._mock To turn on subtensor mocking for testing purposes. - --subtensor.register.num_processes SUBTENSOR.REGISTER.NUM_PROCESSES, -n SUBTENSOR.REGISTER.NUM_PROCESSES - Number of processors to use for registration - --subtensor.register.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, --subtensor.register.cuda.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, --cuda.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, -u SUBTENSOR.REGISTER.UPDATE_INTERVAL - The number of nonces to process before checking for next block during registration - --subtensor.register.no_output_in_place, --no_output_in_place - Whether to not ouput the registration statistics in-place. Set flag to disable output in-place. - --subtensor.register.verbose - Whether to ouput the registration statistics verbosely. - --subtensor.register.cuda.use_cuda, --cuda, --cuda.use_cuda - Set flag to use CUDA to register. - --subtensor.register.cuda.no_cuda, --no_cuda, --cuda.no_cuda - Set flag to not use CUDA for registration - --subtensor.register.cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...], --cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...] - Set the CUDA device id(s). Goes by the order of speed. (i.e. 0 is the fastest). - --subtensor.register.cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB, --cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB - Set the number of Threads Per Block for CUDA. - --logging.debug Turn on bittensor debugging information - --logging.trace Turn on bittensor trace level information - --logging.record_log Turns on logging to file. - --logging.logging_dir LOGGING.LOGGING_DIR - Logging default root directory. - --config CONFIG If set, defaults are overridden by passed file. - --strict If flagged, config will check that only exact arguemnts have been set. -``` \ No newline at end of file diff --git a/neurons/text/prompting/miners/huggingface/robertmyers/neuron.py b/neurons/text/prompting/miners/huggingface/robertmyers/neuron.py deleted file mode 100644 index 7a9bddb941..0000000000 --- a/neurons/text/prompting/miners/huggingface/robertmyers/neuron.py +++ /dev/null @@ -1,62 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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. - -# General. -import torch -import bittensor -from typing import List, Dict -from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline - - -class RobertMyersMiner(bittensor.HuggingFaceMiner): - arg_prefix: str = "robertmyers" - system_label: str = "system:" - assistant_label: str = "assistant:" - user_label: str = "user:" - - def load_tokenizer(self): - return AutoTokenizer.from_pretrained(self.config.robertmyers.model_name) - - def load_model(self): - model = AutoModelForCausalLM.from_pretrained( - self.config.robertmyers.model_name, torch_dtype=torch.float16 - ) - model.to(self.config.robertmyers.device) - return pipeline( - "text-generation", - model, - tokenizer=self.tokenizer, - device=0, - max_new_tokens=self.config.robertmyers.max_new_tokens, - temperature=self.config.robertmyers.temperature, - do_sample=self.config.robertmyers.do_sample, - pad_token_id=self.tokenizer.eos_token_id, - ) - - def forward(self, messages: List[Dict[str, str]]) -> str: - history = self.process_history(messages) - resp = ( - self.model(history)[0]["generated_text"] - .split(":")[-1] - .replace(str(history), "") - ) - return resp - - -if __name__ == "__main__": - bittensor.utils.version_checking() - RobertMyersMiner().run() diff --git a/neurons/text/prompting/miners/huggingface/robertmyers/requirements.txt b/neurons/text/prompting/miners/huggingface/robertmyers/requirements.txt deleted file mode 100644 index bab195ea3c..0000000000 --- a/neurons/text/prompting/miners/huggingface/robertmyers/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -xformers \ No newline at end of file diff --git a/neurons/text/prompting/miners/huggingface/stabilityai/README.md b/neurons/text/prompting/miners/huggingface/stabilityai/README.md deleted file mode 100644 index f77f615690..0000000000 --- a/neurons/text/prompting/miners/huggingface/stabilityai/README.md +++ /dev/null @@ -1,158 +0,0 @@ -## StabilityAI Miner -StabilityAI 7B completion miner for bittensor's prompting network. - -# Example Usage -``` -python3 -m pip install -r neurons/text/prompting/miners/huggingface/stabilityai/requirements.txt -python3 neurons/text/prompting/miners/huggingface/stabilityai/neuron.py --stabilityai.model_size 7 # for 7B model - -# Some suggested settings -python3 neurons/text/prompting/miners/huggingface/stabilityai/neuron.py --stabilityai.temperature 1.0 --stabilityai.top_k 10 --stabilityai.top_p 0.95 --stabilityai.do_sample -``` - -# Full Usage -``` -usage: stabilityai_miner.py [-h] [--stabilityai.model_name STABILITYAI.MODEL_NAME] [--stabilityai.api_key STABILITYAI.API_KEY] - [--stabilityai.device STABILITYAI.DEVICE] [--stabilityai.max_new_tokens STABILITYAI.MAX_NEW_TOKENS] - [--stabilityai.temperature STABILITYAI.TEMPERATURE] [--stabilityai.do_sample] - [--stabilityai.repetition_penalty STABILITYAI.REPETITION_PENALTY] [--stabilityai.do_prompt_injection] - [--stabilityai.system_prompt STABILITYAI.SYSTEM_PROMPT] - [--stabilityai.repetition-penalty STABILITYAI.REPETITION_PENALTY] [--stabilityai.top_p STABILITYAI.TOP_P] - [--stabilityai.top_k STABILITYAI.TOP_K] [--stabilityai.load_in_8bit STABILITYAI.LOAD_IN_8BIT] - [--stabilityai.pad_tokens STABILITYAI.PAD_TOKENS [STABILITYAI.PAD_TOKENS ...]] [--stabilityai.model_size {3,7}] - [--stabilityai.suffix STABILITYAI.SUFFIX] [--stabilityai.num_return_sequences STABILITYAI.NUM_RETURN_SEQUENCES] - [--stabilityai.num_beams STABILITYAI.NUM_BEAMS] [--stabilityai.stopping_criteria STABILITYAI.STOPPING_CRITERIA] - [--netuid NETUID] [--neuron.name NEURON.NAME] [--neuron.blocks_per_epoch NEURON.BLOCKS_PER_EPOCH] - [--neuron.no_set_weights] [--neuron.max_batch_size NEURON.MAX_BATCH_SIZE] - [--neuron.max_sequence_len NEURON.MAX_SEQUENCE_LEN] - [--neuron.blacklist.hotkeys [NEURON.BLACKLIST.HOTKEYS [NEURON.BLACKLIST.HOTKEYS ...]]] - [--neuron.blacklist.allow_non_registered] [--neuron.blacklist.default_stake NEURON.BLACKLIST.DEFAULT_STAKE] - [--neuron.default_priority NEURON.DEFAULT_PRIORITY] [--wallet.name WALLET.NAME] [--wallet.hotkey WALLET.HOTKEY] - [--wallet.path WALLET.PATH] [--wallet._mock] [--wallet.reregister WALLET.REREGISTER] - [--axon.priority.max_workers AXON.PRIORITY.MAX_WORKERS] [--axon.priority.maxsize AXON.PRIORITY.MAXSIZE] - [--axon.port AXON.PORT] [--axon.ip AXON.IP] [--axon.external_port AXON.EXTERNAL_PORT] - [--axon.external_ip AXON.EXTERNAL_IP] [--axon.max_workers AXON.MAX_WORKERS] - [--axon.maximum_concurrent_rpcs AXON.MAXIMUM_CONCURRENT_RPCS] [--subtensor.network SUBTENSOR.NETWORK] - [--subtensor.chain_endpoint SUBTENSOR.CHAIN_ENDPOINT] [--subtensor._mock] - [--subtensor.register.num_processes SUBTENSOR.REGISTER.NUM_PROCESSES] - [--subtensor.register.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL] [--subtensor.register.no_output_in_place] - [--subtensor.register.verbose] [--subtensor.register.cuda.use_cuda] [--subtensor.register.cuda.no_cuda] - [--subtensor.register.cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...]] - [--subtensor.register.cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB] [--logging.debug] [--logging.trace] [--logging.record_log] - [--logging.logging_dir LOGGING.LOGGING_DIR] [--config CONFIG] [--strict] - -optional arguments: - -h, --help show this help message and exit - --stabilityai.model_name STABILITYAI.MODEL_NAME - Name or path of model to load - --stabilityai.api_key STABILITYAI.API_KEY - huggingface api key - --stabilityai.device STABILITYAI.DEVICE - Device to load model - --stabilityai.max_new_tokens STABILITYAI.MAX_NEW_TOKENS - Max tokens for model output. - --stabilityai.temperature STABILITYAI.TEMPERATURE - Sampling temperature of model - --stabilityai.do_sample - Whether to use multinomial sampling. - --stabilityai.repetition_penalty STABILITYAI.REPETITION_PENALTY - Repetition penalty for model - --stabilityai.do_prompt_injection - Whether to use a custom "system" prompt instead of the one sent by bittensor. - --stabilityai.system_prompt STABILITYAI.SYSTEM_PROMPT - What prompt to replace the system prompt with - --stabilityai.repetition-penalty STABILITYAI.REPETITION_PENALTY - Repetition penalty for greedy decoding. Between 1.0 and infinity. 1.0 means no penalty. Default: 1.0 - --stabilityai.top_p STABILITYAI.TOP_P - Top-p (nucleus) sampling. Defaults to 1.0 (top-k sampling). Must be between 0.0 and 1.0. - --stabilityai.top_k STABILITYAI.TOP_K - Top-k sampling. Defaults to 0 (no top-k sampling). Must be between 0 and 1000. - --stabilityai.load_in_8bit STABILITYAI.LOAD_IN_8BIT - Load model in 8 bit precision - --stabilityai.pad_tokens STABILITYAI.PAD_TOKENS [STABILITYAI.PAD_TOKENS ...] - A list of integers separated by spaces for the pad_tokens. - --stabilityai.model_size {3,7} - Run the 3B or 7B model. - --stabilityai.suffix STABILITYAI.SUFFIX - The suffix that comes after a completion of inserted text. - --stabilityai.num_return_sequences STABILITYAI.NUM_RETURN_SEQUENCES - Description of num_return_sequences - --stabilityai.num_beams STABILITYAI.NUM_BEAMS - Description of num_beams - --stabilityai.stopping_criteria STABILITYAI.STOPPING_CRITERIA - Description of stopping_criteria - --netuid NETUID Subnet netuid - --neuron.name NEURON.NAME - Trials for this miner go in miner.root / (wallet_cold - wallet_hot) / miner.name - --neuron.blocks_per_epoch NEURON.BLOCKS_PER_EPOCH - Blocks until the miner sets weights on chain - --neuron.no_set_weights - If True, the model does not set weights. - --neuron.max_batch_size NEURON.MAX_BATCH_SIZE - The maximum batch size for forward requests. - --neuron.max_sequence_len NEURON.MAX_SEQUENCE_LEN - The maximum sequence length for forward requests. - --neuron.blacklist.hotkeys [NEURON.BLACKLIST.HOTKEYS [NEURON.BLACKLIST.HOTKEYS ...]] - To blacklist certain hotkeys - --neuron.blacklist.allow_non_registered - If True, the miner will allow non-registered hotkeys to mine. - --neuron.blacklist.default_stake NEURON.BLACKLIST.DEFAULT_STAKE - Set default stake for miners. - --neuron.default_priority NEURON.DEFAULT_PRIORITY - Set default priority for miners. - --wallet.name WALLET.NAME - The name of the wallet to unlock for running bittensor (name mock is reserved for mocking this wallet) - --wallet.hotkey WALLET.HOTKEY - The name of wallet's hotkey. - --wallet.path WALLET.PATH - The path to your bittensor wallets - --wallet._mock To turn on wallet mocking for testing purposes. - --wallet.reregister WALLET.REREGISTER - Whether to reregister the wallet if it is not already registered. - --axon.priority.max_workers AXON.PRIORITY.MAX_WORKERS - maximum number of threads in thread pool - --axon.priority.maxsize AXON.PRIORITY.MAXSIZE - maximum size of tasks in priority queue - --axon.port AXON.PORT - The local port this axon endpoint is bound to. i.e. 8091 - --axon.ip AXON.IP The local ip this axon binds to. ie. [::] - --axon.external_port AXON.EXTERNAL_PORT - The public port this axon broadcasts to the network. i.e. 8091 - --axon.external_ip AXON.EXTERNAL_IP - The external ip this axon broadcasts to the network to. ie. [::] - --axon.max_workers AXON.MAX_WORKERS - The maximum number connection handler threads working simultaneously on this endpoint. The grpc server distributes new - worker threads to service requests up to this number. - --axon.maximum_concurrent_rpcs AXON.MAXIMUM_CONCURRENT_RPCS - Maximum number of allowed active connections - --subtensor.network SUBTENSOR.NETWORK - The subtensor network flag. The likely choices are: -- finney (main network) -- local (local running network) -- mock - (creates a mock connection (for testing)) If this option is set it overloads subtensor.chain_endpoint with an entry point - node from that network. - --subtensor.chain_endpoint SUBTENSOR.CHAIN_ENDPOINT - The subtensor endpoint flag. If set, overrides the --network flag. - --subtensor._mock To turn on subtensor mocking for testing purposes. - --subtensor.register.num_processes SUBTENSOR.REGISTER.NUM_PROCESSES, -n SUBTENSOR.REGISTER.NUM_PROCESSES - Number of processors to use for registration - --subtensor.register.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, --subtensor.register.cuda.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, --cuda.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, -u SUBTENSOR.REGISTER.UPDATE_INTERVAL - The number of nonces to process before checking for next block during registration - --subtensor.register.no_output_in_place, --no_output_in_place - Whether to not ouput the registration statistics in-place. Set flag to disable output in-place. - --subtensor.register.verbose - Whether to ouput the registration statistics verbosely. - --subtensor.register.cuda.use_cuda, --cuda, --cuda.use_cuda - Set flag to use CUDA to register. - --subtensor.register.cuda.no_cuda, --no_cuda, --cuda.no_cuda - Set flag to not use CUDA for registration - --subtensor.register.cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...], --cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...] - Set the CUDA device id(s). Goes by the order of speed. (i.e. 0 is the fastest). - --subtensor.register.cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB, --cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB - Set the number of Threads Per Block for CUDA. - --logging.debug Turn on bittensor debugging information - --logging.trace Turn on bittensor trace level information - --logging.record_log Turns on logging to file. - --logging.logging_dir LOGGING.LOGGING_DIR - Logging default root directory. - --config CONFIG If set, defaults are overridden by passed file. - --strict If flagged, config will check that only exact arguemnts have been set. -``` \ No newline at end of file diff --git a/neurons/text/prompting/miners/huggingface/stabilityai/neuron.py b/neurons/text/prompting/miners/huggingface/stabilityai/neuron.py deleted file mode 100644 index 4b11664187..0000000000 --- a/neurons/text/prompting/miners/huggingface/stabilityai/neuron.py +++ /dev/null @@ -1,128 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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. - - -# General. -import torch -import argparse -import bittensor -from typing import List, Dict -from transformers import ( - AutoTokenizer, - AutoModelForCausalLM, - pipeline, - StoppingCriteria, - StoppingCriteriaList, -) - - -class StopOnTokens(StoppingCriteria): - def __call__( - self, input_ids: torch.LongTensor, scores: torch.FloatTensor, **kwargs - ) -> bool: - stop_ids = [50278, 50279, 50277, 1, 0] - for stop_id in stop_ids: - if input_ids[0][-1] == stop_id: - return True - return False - - -class StabilityAIMiner(bittensor.HuggingFaceMiner): - arg_prefix: str = "stabilityai" - system_label: str = "<|SYSTEM|>:" - assistant_label: str = "<|ASSISTANT|>:" - user_label: str = "<|USER|>:" - - @classmethod - def add_args(cls, parser: argparse.ArgumentParser): - super(StabilityAIMiner, cls).add_args(parser) - parser.add_argument( - "--stabilityai.model_size", - type=int, - choices=[3, 7], - default=7, - help="Run the 3B or 7B model.", - ) - parser.add_argument( - "--stabilityai.suffix", - type=str, - default=None, - help="The suffix that comes after a completion of inserted text.", - ) - parser.add_argument( - "--stabilityai.num_return_sequences", - type=int, - default=1, - help="Description of num_return_sequences", - ) - parser.add_argument( - "--stabilityai.num_beams", - type=int, - default=1, - help="Description of num_beams", - ) - parser.add_argument( - "--stabilityai.stopping_criteria", - type=str, - default="stop", - help="Description of stopping_criteria", - ) - - def load_tokenizer(self): - return AutoTokenizer.from_pretrained( - "stabilityai/stablelm-tuned-alpha-{}b".format( - self.config.stabilityai.model_size - ), - use_auth_token=self.config.stabilityai.api_key, - ) - - def load_model(self): - model = AutoModelForCausalLM.from_pretrained( - "stabilityai/stablelm-tuned-alpha-{}b".format( - self.config.stabilityai.model_size - ), - use_auth_token=self.config.stabilityai.api_key, - torch_dtype=torch.float16, - ).cuda() - - return pipeline( - "text-generation", - model, - tokenizer=self.tokenizer, - device=0, - max_new_tokens=self.config.stabilityai.max_new_tokens, - num_return_sequences=self.config.stabilityai.num_return_sequences, - num_beams=self.config.stabilityai.num_beams, - do_sample=self.config.stabilityai.do_sample, - temperature=self.config.stabilityai.temperature, - top_p=self.config.stabilityai.top_p, - top_k=self.config.stabilityai.top_k, - stopping_criteria=StoppingCriteriaList([StopOnTokens()]), - ) - - def forward(self, messages: List[Dict[str, str]]) -> str: - history = self.process_history(messages) - return ( - self.model(history)[0]["generated_text"] - .split(":")[-1] - .replace(str(history), "") - ) - - -if __name__ == "__main__": - bittensor.utils.version_checking() - StabilityAIMiner().run() diff --git a/neurons/text/prompting/miners/huggingface/stabilityai/stabilityai_requirements.txt b/neurons/text/prompting/miners/huggingface/stabilityai/stabilityai_requirements.txt deleted file mode 100644 index 747b7aa97a..0000000000 --- a/neurons/text/prompting/miners/huggingface/stabilityai/stabilityai_requirements.txt +++ /dev/null @@ -1 +0,0 @@ -transformers \ No newline at end of file diff --git a/neurons/text/prompting/miners/huggingface/vicuna/README.md b/neurons/text/prompting/miners/huggingface/vicuna/README.md deleted file mode 100644 index e7d2f55f8a..0000000000 --- a/neurons/text/prompting/miners/huggingface/vicuna/README.md +++ /dev/null @@ -1,183 +0,0 @@ -## Vicuna Miner -Vicuna Language Model Serving with BitTensor -This code is for running the Vicuna model through the BitTensor framework. - -# Overview - -## Contents - -- [Installing Dependencies](#installing-dependencies) -- [Converting Weights Into Model](#converting-weights-into-model) -- [Starting Miner](#starting-miner) - - -# Installing Dependencies - -``` -python3 -m pip install -r neurons/text/prompting/miners/huggingface/vicuna/requirements.txt -``` - -# Converting Weights Into Model -If you already have a converted checkpoint of the model, you can skip this step. - -## Vicuna Weights -The [Vicuna](https://vicuna.lmsys.org/) weights as delta weights to comply with the LLaMA model license. -You can add our delta to the original LLaMA weights to obtain the Vicuna weights. Instructions: - -1. Get the original LLaMA weights in the huggingface format by following the instructions [here](https://huggingface.co/docs/transformers/main/model_doc/llama). -2. Use the following scripts to get Vicuna weights by applying our delta. They will automatically download delta weights from our Hugging Face [account](https://huggingface.co/lmsys). - -**NOTE**: -Weights v1.1 are only compatible with the latest main branch of huggingface/transformers and ``fschat >= 0.2.0``. -Please update your local packages accordingly. If you follow the above commands to do a fresh install, then you should get all the correct versions. - -Depending on which conversion script was used to create the Huggingface checkpoint of Llama, you might get an error that the tokenizer can not be found when loading the tokenizer. You can then replace all AutoTokenizers command with the correct tokenizer (in the example "LlamaTokenizer"), using this command: -``` -find /path/to/fastchat -type f -name '*.py' -exec sed -i 's/AutoTokenizer/LlamaTokenizer/g' {} + -``` - -### Vicuna-7B -This conversion command needs around 30 GB of CPU RAM. -If you do not have enough memory, you can create a large swap file that allows the operating system to automatically utilize the disk as virtual memory. -```bash -python3 -m fastchat.model.apply_delta \ - --base /path/to/llama-7b \ - --target /output/path/to/vicuna-7b \ - --delta lmsys/vicuna-7b-delta-v1.1 -``` - -### Vicuna-13B -This conversion command needs around 60 GB of CPU RAM. -If you do not have enough memory, you can create a large swap file that allows the operating system to automatically utilize the disk as virtual memory. -```bash -python3 -m fastchat.model.apply_delta \ - --base /path/to/llama-13b \ - --target /output/path/to/vicuna-13b \ - --delta lmsys/vicuna-13b-delta-v1.1 -``` - - -# Starting Miner -``` -# If using HuggingFace model directly, only need to supply the repo ID. -python3 neurons/text/prompting/miners/huggingface/vicuna/neuron.py --vicuna.model_name - -# If merging the weights yourself supply the path. -python3 neurons/text/prompting/miners/huggingface/vicuna/neuron.py --vicuna.model_name /path/to/merged/vicuna/weights - -``` - -# Full Usage -``` -usage: vicuna_miner.py [-h] --vicuna.model_name VICUNA.MODEL_NAME [--vicuna.device VICUNA.DEVICE] [--vicuna.max_new_tokens VICUNA.MAX_NEW_TOKENS] [--vicuna.temperature VICUNA.TEMPERATURE] [--vicuna.do_sample] - [--vicuna.repetition_penalty VICUNA.REPETITION_PENALTY] [--vicuna.do_prompt_injection] [--vicuna.system_prompt VICUNA.SYSTEM_PROMPT] [--vicuna.repetition-penalty VICUNA.REPETITION_PENALTY] [--vicuna.top_p VICUNA.TOP_P] - [--vicuna.top_k VICUNA.TOP_K] [--vicuna.load_in_8bit VICUNA.LOAD_IN_8BIT] [--vicuna.pad_tokens VICUNA.PAD_TOKENS [VICUNA.PAD_TOKENS ...]] [--netuid NETUID] [--neuron.name NEURON.NAME] [--neuron.blocks_per_epoch NEURON.BLOCKS_PER_EPOCH] - [--neuron.no_set_weights] [--neuron.max_batch_size NEURON.MAX_BATCH_SIZE] [--neuron.max_sequence_len NEURON.MAX_SEQUENCE_LEN] [--neuron.blacklist.hotkeys [NEURON.BLACKLIST.HOTKEYS [NEURON.BLACKLIST.HOTKEYS ...]]] - [--neuron.blacklist.allow_non_registered] [--neuron.blacklist.default_stake NEURON.BLACKLIST.DEFAULT_STAKE] [--neuron.default_priority NEURON.DEFAULT_PRIORITY] [--wallet.name WALLET.NAME] [--wallet.hotkey WALLET.HOTKEY] - [--wallet.path WALLET.PATH] [--wallet._mock] [--wallet.reregister WALLET.REREGISTER] [--axon.priority.max_workers AXON.PRIORITY.MAX_WORKERS] [--axon.priority.maxsize AXON.PRIORITY.MAXSIZE] [--axon.port AXON.PORT] [--axon.ip AXON.IP] - [--axon.external_port AXON.EXTERNAL_PORT] [--axon.external_ip AXON.EXTERNAL_IP] [--axon.max_workers AXON.MAX_WORKERS] [--axon.maximum_concurrent_rpcs AXON.MAXIMUM_CONCURRENT_RPCS] [--subtensor.network SUBTENSOR.NETWORK] - [--subtensor.chain_endpoint SUBTENSOR.CHAIN_ENDPOINT] [--subtensor._mock] [--subtensor.register.num_processes SUBTENSOR.REGISTER.NUM_PROCESSES] [--subtensor.register.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL] - [--subtensor.register.no_output_in_place] [--subtensor.register.verbose] [--subtensor.register.cuda.use_cuda] [--subtensor.register.cuda.no_cuda] - [--subtensor.register.cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...]] [--subtensor.register.cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB] [--logging.debug] [--logging.trace] [--logging.record_log] - [--logging.logging_dir LOGGING.LOGGING_DIR] [--config CONFIG] [--strict] - -optional arguments: - -h, --help show this help message and exit - --vicuna.model_name VICUNA.MODEL_NAME - Name or path of model to load - --vicuna.device VICUNA.DEVICE - Device to load model - --vicuna.max_new_tokens VICUNA.MAX_NEW_TOKENS - Max tokens for model output. - --vicuna.temperature VICUNA.TEMPERATURE - Sampling temperature of model - --vicuna.do_sample Whether to use multinomial sampling. - --vicuna.repetition_penalty VICUNA.REPETITION_PENALTY - Repetition penalty for model - --vicuna.do_prompt_injection - Whether to use a custom "system" prompt instead of the one sent by bittensor. - --vicuna.system_prompt VICUNA.SYSTEM_PROMPT - What prompt to replace the system prompt with - --vicuna.repetition-penalty VICUNA.REPETITION_PENALTY - Repetition penalty for greedy decoding. Between 1.0 and infinity. 1.0 means no penalty. Default: 1.0 - --vicuna.top_p VICUNA.TOP_P - Top-p (nucleus) sampling. Defaults to 1.0 (top-k sampling). Must be between 0.0 and 1.0. - --vicuna.top_k VICUNA.TOP_K - Top-k sampling. Defaults to 0 (no top-k sampling). Must be between 0 and 1000. - --vicuna.load_in_8bit VICUNA.LOAD_IN_8BIT - Load model in 8 bit precision - --vicuna.pad_tokens VICUNA.PAD_TOKENS [VICUNA.PAD_TOKENS ...] - A list of integers separated by spaces for the pad_tokens. - --netuid NETUID Subnet netuid - --neuron.name NEURON.NAME - Trials for this miner go in miner.root / (wallet_cold - wallet_hot) / miner.name - --neuron.blocks_per_epoch NEURON.BLOCKS_PER_EPOCH - Blocks until the miner sets weights on chain - --neuron.no_set_weights - If True, the model does not set weights. - --neuron.max_batch_size NEURON.MAX_BATCH_SIZE - The maximum batch size for forward requests. - --neuron.max_sequence_len NEURON.MAX_SEQUENCE_LEN - The maximum sequence length for forward requests. - --neuron.blacklist.hotkeys [NEURON.BLACKLIST.HOTKEYS [NEURON.BLACKLIST.HOTKEYS ...]] - To blacklist certain hotkeys - --neuron.blacklist.allow_non_registered - If True, the miner will allow non-registered hotkeys to mine. - --neuron.blacklist.default_stake NEURON.BLACKLIST.DEFAULT_STAKE - Set default stake for miners. - --neuron.default_priority NEURON.DEFAULT_PRIORITY - Set default priority for miners. - --wallet.name WALLET.NAME - The name of the wallet to unlock for running bittensor (name mock is reserved for mocking this wallet) - --wallet.hotkey WALLET.HOTKEY - The name of wallet's hotkey. - --wallet.path WALLET.PATH - The path to your bittensor wallets - --wallet._mock To turn on wallet mocking for testing purposes. - --wallet.reregister WALLET.REREGISTER - Whether to reregister the wallet if it is not already registered. - --axon.priority.max_workers AXON.PRIORITY.MAX_WORKERS - maximum number of threads in thread pool - --axon.priority.maxsize AXON.PRIORITY.MAXSIZE - maximum size of tasks in priority queue - --axon.port AXON.PORT - The local port this axon endpoint is bound to. i.e. 8091 - --axon.ip AXON.IP The local ip this axon binds to. ie. [::] - --axon.external_port AXON.EXTERNAL_PORT - The public port this axon broadcasts to the network. i.e. 8091 - --axon.external_ip AXON.EXTERNAL_IP - The external ip this axon broadcasts to the network to. ie. [::] - --axon.max_workers AXON.MAX_WORKERS - The maximum number connection handler threads working simultaneously on this endpoint. The grpc server distributes new worker threads to service requests up to this number. - --axon.maximum_concurrent_rpcs AXON.MAXIMUM_CONCURRENT_RPCS - Maximum number of allowed active connections - --subtensor.network SUBTENSOR.NETWORK - The subtensor network flag. The likely choices are: -- finney (main network) -- local (local running network) -- mock (creates a mock connection (for testing)) If this option is set it overloads subtensor.chain_endpoint with an entry - point node from that network. - --subtensor.chain_endpoint SUBTENSOR.CHAIN_ENDPOINT - The subtensor endpoint flag. If set, overrides the --network flag. - --subtensor._mock To turn on subtensor mocking for testing purposes. - --subtensor.register.num_processes SUBTENSOR.REGISTER.NUM_PROCESSES, -n SUBTENSOR.REGISTER.NUM_PROCESSES - Number of processors to use for registration - --subtensor.register.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, --subtensor.register.cuda.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, --cuda.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, -u SUBTENSOR.REGISTER.UPDATE_INTERVAL - The number of nonces to process before checking for next block during registration - --subtensor.register.no_output_in_place, --no_output_in_place - Whether to not ouput the registration statistics in-place. Set flag to disable output in-place. - --subtensor.register.verbose - Whether to ouput the registration statistics verbosely. - --subtensor.register.cuda.use_cuda, --cuda, --cuda.use_cuda - Set flag to use CUDA to register. - --subtensor.register.cuda.no_cuda, --no_cuda, --cuda.no_cuda - Set flag to not use CUDA for registration - --subtensor.register.cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...], --cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...] - Set the CUDA device id(s). Goes by the order of speed. (i.e. 0 is the fastest). - --subtensor.register.cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB, --cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB - Set the number of Threads Per Block for CUDA. - --logging.debug Turn on bittensor debugging information - --logging.trace Turn on bittensor trace level information - --logging.record_log Turns on logging to file. - --logging.logging_dir LOGGING.LOGGING_DIR - Logging default root directory. - --config CONFIG If set, defaults are overridden by passed file. - --strict If flagged, config will check that only exact arguemnts have been set. -``` \ No newline at end of file diff --git a/neurons/text/prompting/miners/huggingface/vicuna/neuron.py b/neurons/text/prompting/miners/huggingface/vicuna/neuron.py deleted file mode 100644 index 5d89d29440..0000000000 --- a/neurons/text/prompting/miners/huggingface/vicuna/neuron.py +++ /dev/null @@ -1,74 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2023 Yuma Rao - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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 -from typing import List, Dict -from transformers import AutoTokenizer, AutoModelForCausalLM - - -class VicunaMiner(bittensor.HuggingFaceMiner): - arg_prefix: str = "vicuna" - system_label: str = "" - assistant_label: str = "ASSISTANT:" - user_label: str = "USER:" - - def __init__(self): - super(VicunaMiner, self).__init__() - print(self.config) - - def load_tokenizer(self): - return AutoTokenizer.from_pretrained( - self.config.vicuna.model_name, use_fast=False - ) - - def load_model(self): - return AutoModelForCausalLM.from_pretrained( - self.config.vicuna.model_name, - torch_dtype=torch.float16, - low_cpu_mem_usage=True, - ) - - def forward(self, messages: List[Dict[str, str]]) -> str: - history = self.process_history(messages) - prompt = history + self.assistant_label - print(prompt) - - input_ids = self.tokenizer.encode(prompt, return_tensors="pt").to( - self.config.vicuna.device - ) - output = self.model.generate( - input_ids, - max_length=input_ids.shape[1] + self.config.vicuna.max_new_tokens, - temperature=self.config.vicuna.temperature, - do_sample=self.config.vicuna.do_sample, - pad_token_id=self.tokenizer.eos_token_id, - ) - - generation = self.tokenizer.decode( - output[0][input_ids.shape[1] :], skip_special_tokens=True - ) - print(generation) - - bittensor.logging.debug("Message: " + str(messages)) - bittensor.logging.debug("Generation: " + str(generation)) - return generation - - -if __name__ == "__main__": - bittensor.utils.version_checking() - VicunaMiner().run() diff --git a/neurons/text/prompting/miners/huggingface/vicuna/requirements.txt b/neurons/text/prompting/miners/huggingface/vicuna/requirements.txt deleted file mode 100644 index 5e83b53a0d..0000000000 --- a/neurons/text/prompting/miners/huggingface/vicuna/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -transformers>=4.28.0 -fschat -tokenizers>=0.13.3 -accelerate \ No newline at end of file diff --git a/neurons/text/prompting/miners/huggingface/wizard_vicuna/README.md b/neurons/text/prompting/miners/huggingface/wizard_vicuna/README.md deleted file mode 100644 index 699dcd2b26..0000000000 --- a/neurons/text/prompting/miners/huggingface/wizard_vicuna/README.md +++ /dev/null @@ -1,117 +0,0 @@ - -## WizardLM + Vicuna Miner -WizardLM Vicuna completion miner for bittensor's prompting network. - -# Example Usage -``` -python3 neurons/text/prompting/miners/huggingface/wizard_vicuna/neuron.py --wiz_vic.model_name -``` - -# Full Usage -``` -usage: neuron.py [-h] [--wiz_vic.model_name WIZ_VIC.MODEL_NAME] [--wiz_vic.device WIZ_VIC.DEVICE] [--wiz_vic.max_new_tokens WIZ_VIC.MAX_NEW_TOKENS] - [--wiz_vic.temperature WIZ_VIC.TEMPERATURE] [--wiz_vic.greedy_decoding] [--wiz_vic.do_sample] [--wiz_vic.do_prompt_injection] - [--wiz_vic.system_prompt WIZ_VIC.SYSTEM_PROMPT] [--netuid NETUID] [--neuron.name NEURON.NAME] [--neuron.blocks_per_epoch NEURON.BLOCKS_PER_EPOCH] - [--neuron.no_set_weights] [--neuron.max_batch_size NEURON.MAX_BATCH_SIZE] [--neuron.max_sequence_len NEURON.MAX_SEQUENCE_LEN] - [--neuron.blacklist.hotkeys [NEURON.BLACKLIST.HOTKEYS [NEURON.BLACKLIST.HOTKEYS ...]]] [--neuron.blacklist.allow_non_registered] - [--neuron.blacklist.default_stake NEURON.BLACKLIST.DEFAULT_STAKE] [--neuron.default_priority NEURON.DEFAULT_PRIORITY] [--wallet.name WALLET.NAME] - [--wallet.hotkey WALLET.HOTKEY] [--wallet.path WALLET.PATH] [--wallet._mock] [--wallet.reregister WALLET.REREGISTER] - [--axon.priority.max_workers AXON.PRIORITY.MAX_WORKERS] [--axon.priority.maxsize AXON.PRIORITY.MAXSIZE] [--axon.port AXON.PORT] [--axon.ip AXON.IP] - [--axon.external_port AXON.EXTERNAL_PORT] [--axon.external_ip AXON.EXTERNAL_IP] [--axon.max_workers AXON.MAX_WORKERS] - [--axon.maximum_concurrent_rpcs AXON.MAXIMUM_CONCURRENT_RPCS] [--subtensor.network SUBTENSOR.NETWORK] [--subtensor.chain_endpoint SUBTENSOR.CHAIN_ENDPOINT] - [--subtensor._mock] [--subtensor.register.num_processes SUBTENSOR.REGISTER.NUM_PROCESSES] [--subtensor.register.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL] - [--subtensor.register.no_output_in_place] [--subtensor.register.verbose] [--subtensor.register.cuda.use_cuda] [--subtensor.register.cuda.no_cuda] - [--subtensor.register.cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...]] [--subtensor.register.cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB] - [--logging.debug] [--logging.trace] [--logging.record_log] [--logging.logging_dir LOGGING.LOGGING_DIR] [--config CONFIG] [--strict] - -optional arguments: - -h, --help show this help message and exit - --wiz_vic.model_name WIZ_VIC.MODEL_NAME - Name/path of model to load - --wiz_vic.device WIZ_VIC.DEVICE - Device to load model - --wiz_vic.max_new_tokens WIZ_VIC.MAX_NEW_TOKENS - Max tokens for model output. - --wiz_vic.temperature WIZ_VIC.TEMPERATURE - Sampling temperature of model - --wiz_vic.greedy_decoding - Whether to use greedy sampling or not (if not, uses multinomial sampling). - --wiz_vic.do_sample Whether to use sampling or not (if not, uses greedy decoding). - --wiz_vic.do_prompt_injection - Whether to use a custom "system" prompt instead of the one sent by bittensor. - --wiz_vic.system_prompt WIZ_VIC.SYSTEM_PROMPT - What prompt to replace the system prompt with - --netuid NETUID Subnet netuid - --neuron.name NEURON.NAME - Trials for this miner go in miner.root / (wallet_cold - wallet_hot) / miner.name - --neuron.blocks_per_epoch NEURON.BLOCKS_PER_EPOCH - Blocks until the miner sets weights on chain - --neuron.no_set_weights - If True, the model does not set weights. - --neuron.max_batch_size NEURON.MAX_BATCH_SIZE - The maximum batch size for forward requests. - --neuron.max_sequence_len NEURON.MAX_SEQUENCE_LEN - The maximum sequence length for forward requests. - --neuron.blacklist.hotkeys [NEURON.BLACKLIST.HOTKEYS [NEURON.BLACKLIST.HOTKEYS ...]] - To blacklist certain hotkeys - --neuron.blacklist.allow_non_registered - If True, the miner will allow non-registered hotkeys to mine. - --neuron.blacklist.default_stake NEURON.BLACKLIST.DEFAULT_STAKE - Set default stake for miners. - --neuron.default_priority NEURON.DEFAULT_PRIORITY - Set default priority for miners. - --wallet.name WALLET.NAME - The name of the wallet to unlock for running bittensor (name mock is reserved for mocking this wallet) - --wallet.hotkey WALLET.HOTKEY - The name of wallet's hotkey. - --wallet.path WALLET.PATH - The path to your bittensor wallets - --wallet._mock To turn on wallet mocking for testing purposes. - --wallet.reregister WALLET.REREGISTER - Whether to reregister the wallet if it is not already registered. - --axon.priority.max_workers AXON.PRIORITY.MAX_WORKERS - maximum number of threads in thread pool - --axon.priority.maxsize AXON.PRIORITY.MAXSIZE - maximum size of tasks in priority queue - --axon.port AXON.PORT - The local port this axon endpoint is bound to. i.e. 8091 - --axon.ip AXON.IP The local ip this axon binds to. ie. [::] - --axon.external_port AXON.EXTERNAL_PORT - The public port this axon broadcasts to the network. i.e. 8091 - --axon.external_ip AXON.EXTERNAL_IP - The external ip this axon broadcasts to the network to. ie. [::] - --axon.max_workers AXON.MAX_WORKERS - The maximum number connection handler threads working simultaneously on this endpoint. The grpc server distributes new worker threads to service requests up - to this number. - --axon.maximum_concurrent_rpcs AXON.MAXIMUM_CONCURRENT_RPCS - Maximum number of allowed active connections - --subtensor.network SUBTENSOR.NETWORK - The subtensor network flag. The likely choices are: -- finney (main network) -- local (local running network) -- mock (creates a mock connection (for - testing)) If this option is set it overloads subtensor.chain_endpoint with an entry point node from that network. - --subtensor.chain_endpoint SUBTENSOR.CHAIN_ENDPOINT - The subtensor endpoint flag. If set, overrides the --network flag. - --subtensor._mock To turn on subtensor mocking for testing purposes. - --subtensor.register.num_processes SUBTENSOR.REGISTER.NUM_PROCESSES, -n SUBTENSOR.REGISTER.NUM_PROCESSES - Number of processors to use for registration - --subtensor.register.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, --subtensor.register.cuda.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, --cuda.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, -u SUBTENSOR.REGISTER.UPDATE_INTERVAL - The number of nonces to process before checking for next block during registration - --subtensor.register.no_output_in_place, --no_output_in_place - Whether to not ouput the registration statistics in-place. Set flag to disable output in-place. - --subtensor.register.verbose - Whether to ouput the registration statistics verbosely. - --subtensor.register.cuda.use_cuda, --cuda, --cuda.use_cuda - Set flag to use CUDA to register. - --subtensor.register.cuda.no_cuda, --no_cuda, --cuda.no_cuda - Set flag to not use CUDA for registration - --subtensor.register.cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...], --cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...] - Set the CUDA device id(s). Goes by the order of speed. (i.e. 0 is the fastest). - --subtensor.register.cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB, --cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB - Set the number of Threads Per Block for CUDA. - --logging.debug Turn on bittensor debugging information - --logging.trace Turn on bittensor trace level information - --logging.record_log Turns on logging to file. - --logging.logging_dir LOGGING.LOGGING_DIR - Logging default root directory. - --config CONFIG If set, defaults are overridden by passed file. - --strict If flagged, config will check that only exact arguemnts have been set. -``` \ No newline at end of file diff --git a/neurons/text/prompting/miners/huggingface/wizard_vicuna/neuron.py b/neurons/text/prompting/miners/huggingface/wizard_vicuna/neuron.py deleted file mode 100644 index 9ae57b3d8c..0000000000 --- a/neurons/text/prompting/miners/huggingface/wizard_vicuna/neuron.py +++ /dev/null @@ -1,66 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2023 Opentensor Foundation - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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 -from typing import List, Dict -from transformers import AutoTokenizer, AutoModelForCausalLM - - -class WizardVicunaMiner(bittensor.HuggingFaceMiner): - arg_prefix = "wiz_vic" - system_label = "" - assistant_label = "ASSISTANT:" - user_label = "USER:" - - def load_tokenizer(self): - return AutoTokenizer.from_pretrained( - self.config.wiz_vic.model_name, use_fast=False - ) - - def load_model(self): - return AutoModelForCausalLM.from_pretrained( - self.config.wiz_vic.model_name, - torch_dtype=torch.float16, - low_cpu_mem_usage=True, - ) - - def forward(self, messages: List[Dict[str, str]]) -> str: - history = self.process_history(messages) - prompt = history + self.assistant_label - input_ids = self.tokenizer.encode(prompt, return_tensors="pt").to( - self.config.wiz_vic.device - ) - output = self.model.generate( - input_ids, - max_length=input_ids.shape[1] + self.config.wiz_vic.max_new_tokens, - temperature=self.config.wiz_vic.temperature, - do_sample=self.config.wiz_vic.do_sample, - pad_token_id=self.tokenizer.eos_token_id, - ) - generation = self.tokenizer.decode( - output[0][input_ids.shape[1] :], skip_special_tokens=True - ) - - bittensor.logging.debug("Message: " + str(messages)) - bittensor.logging.debug("Generation: " + str(generation)) - return generation - - -if __name__ == "__main__": - bittensor.utils.version_checking() - WizardVicunaMiner().run() diff --git a/neurons/text/prompting/miners/huggingface/wizard_vicuna/requirements.txt b/neurons/text/prompting/miners/huggingface/wizard_vicuna/requirements.txt deleted file mode 100644 index 33059ec77c..0000000000 --- a/neurons/text/prompting/miners/huggingface/wizard_vicuna/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -transformers>=4.29.2 -accelerate \ No newline at end of file diff --git a/neurons/text/prompting/miners/openai/README.md b/neurons/text/prompting/miners/openai/README.md deleted file mode 100644 index c7b6ee8663..0000000000 --- a/neurons/text/prompting/miners/openai/README.md +++ /dev/null @@ -1,143 +0,0 @@ -# OpenAI Bittensor Miner -This repository contains a Bittensor Miner that uses OpenAI's GPT-3.5-turbo model as its synapse. The miner connects to the Bittensor network, registers its wallet, and serves the GPT-3.5-turbo model to the network. - -## Prerequisites - -- Python 3.8+ -- OpenAI Python API (https://github.com/openai/openai) - -## Installation - -1. Clone the repository -2. Install the required packages with `pip install -r requirements.txt` -3. Set your OpenAI API key in the `api_key` argument when running the script - -For more configuration options related to the wallet, axon, subtensor, logging, and metagraph, please refer to the Bittensor documentation. - -## Example Usage - -To run the OpenAI Bittensor Miner with default settings, use the following command: - -``` -python3 -m pip install -r neurons/text/prompting/miners/openai/requirements.txt -python3 neurons/text/prompting/miners/openai/neuron.py --openai.api_key -``` - -# Full Usage -``` -usage: neuron.py [-h] [--openai.api_key OPENAI.API_KEY] [--openai.suffix OPENAI.SUFFIX] [--openai.max_tokens OPENAI.MAX_TOKENS] - [--openai.temperature OPENAI.TEMPERATURE] [--openai.top_p OPENAI.TOP_P] [--openai.n OPENAI.N] - [--openai.presence_penalty OPENAI.PRESENCE_PENALTY] [--openai.frequency_penalty OPENAI.FREQUENCY_PENALTY] - [--openai.model_name OPENAI.MODEL_NAME] [--netuid NETUID] [--neuron.name NEURON.NAME] - [--neuron.blocks_per_epoch NEURON.BLOCKS_PER_EPOCH] [--neuron.no_set_weights] - [--neuron.max_batch_size NEURON.MAX_BATCH_SIZE] [--neuron.max_sequence_len NEURON.MAX_SEQUENCE_LEN] - [--neuron.blacklist.hotkeys [NEURON.BLACKLIST.HOTKEYS ...]] [--neuron.blacklist.allow_non_registered] - [--neuron.blacklist.default_stake NEURON.BLACKLIST.DEFAULT_STAKE] [--neuron.default_priority NEURON.DEFAULT_PRIORITY] - [--wallet.name WALLET.NAME] [--wallet.hotkey WALLET.HOTKEY] [--wallet.path WALLET.PATH] [--wallet._mock] - [--wallet.reregister WALLET.REREGISTER] [--axon.priority.max_workers AXON.PRIORITY.MAX_WORKERS] - [--axon.priority.maxsize AXON.PRIORITY.MAXSIZE] [--axon.port AXON.PORT] [--axon.ip AXON.IP] - [--axon.external_port AXON.EXTERNAL_PORT] [--axon.external_ip AXON.EXTERNAL_IP] [--axon.max_workers AXON.MAX_WORKERS] - [--axon.maximum_concurrent_rpcs AXON.MAXIMUM_CONCURRENT_RPCS] [--subtensor.network SUBTENSOR.NETWORK] - [--subtensor.chain_endpoint SUBTENSOR.CHAIN_ENDPOINT] [--subtensor._mock] - [--subtensor.register.num_processes SUBTENSOR.REGISTER.NUM_PROCESSES] - [--subtensor.register.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL] [--subtensor.register.no_output_in_place] - [--subtensor.register.verbose] [--subtensor.register.cuda.use_cuda] [--subtensor.register.cuda.no_cuda] - [--subtensor.register.cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...]] - [--subtensor.register.cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB] [--logging.debug] [--logging.trace] [--logging.record_log] - [--logging.logging_dir LOGGING.LOGGING_DIR] [--metagraph._mock] [--config CONFIG] [--strict] - -optional arguments: - -h, --help show this help message and exit - --openai.api_key OPENAI.API_KEY - openai api key - --openai.suffix OPENAI.SUFFIX - The suffix that comes after a completion of inserted text. - --openai.max_tokens OPENAI.MAX_TOKENS - The maximum number of tokens to generate in the completion. - --openai.temperature OPENAI.TEMPERATURE - Sampling temperature to use, between 0 and 2. - --openai.top_p OPENAI.TOP_P - Nucleus sampling parameter, top_p probability mass. - --openai.n OPENAI.N How many completions to generate for each prompt. - --openai.presence_penalty OPENAI.PRESENCE_PENALTY - Penalty for tokens based on their presence in the text so far. - --openai.frequency_penalty OPENAI.FREQUENCY_PENALTY - Penalty for tokens based on their frequency in the text so far. - --openai.model_name OPENAI.MODEL_NAME - OpenAI model to use for completion. - --netuid NETUID Subnet netuid - --neuron.name NEURON.NAME - Trials for this miner go in miner.root / (wallet_cold - wallet_hot) / miner.name - --neuron.blocks_per_epoch NEURON.BLOCKS_PER_EPOCH - Blocks until the miner sets weights on chain - --neuron.no_set_weights - If True, the model does not set weights. - --neuron.max_batch_size NEURON.MAX_BATCH_SIZE - The maximum batch size for forward requests. - --neuron.max_sequence_len NEURON.MAX_SEQUENCE_LEN - The maximum sequence length for forward requests. - --neuron.blacklist.hotkeys [NEURON.BLACKLIST.HOTKEYS ...] - To blacklist certain hotkeys - --neuron.blacklist.allow_non_registered - If True, the miner will allow non-registered hotkeys to mine. - --neuron.blacklist.default_stake NEURON.BLACKLIST.DEFAULT_STAKE - Set default stake for miners. - --neuron.default_priority NEURON.DEFAULT_PRIORITY - Set default priority for miners. - --wallet.name WALLET.NAME - The name of the wallet to unlock for running bittensor (name mock is reserved for mocking this wallet) - --wallet.hotkey WALLET.HOTKEY - The name of wallet's hotkey. - --wallet.path WALLET.PATH - The path to your bittensor wallets - --wallet._mock To turn on wallet mocking for testing purposes. - --wallet.reregister WALLET.REREGISTER - Whether to reregister the wallet if it is not already registered. - --axon.priority.max_workers AXON.PRIORITY.MAX_WORKERS - maximum number of threads in thread pool - --axon.priority.maxsize AXON.PRIORITY.MAXSIZE - maximum size of tasks in priority queue - --axon.port AXON.PORT - The local port this axon endpoint is bound to. i.e. 8091 - --axon.ip AXON.IP The local ip this axon binds to. ie. [::] - --axon.external_port AXON.EXTERNAL_PORT - The public port this axon broadcasts to the network. i.e. 8091 - --axon.external_ip AXON.EXTERNAL_IP - The external ip this axon broadcasts to the network to. ie. [::] - --axon.max_workers AXON.MAX_WORKERS - The maximum number connection handler threads working simultaneously on this endpoint. The grpc server distributes - new worker threads to service requests up to this number. - --axon.maximum_concurrent_rpcs AXON.MAXIMUM_CONCURRENT_RPCS - Maximum number of allowed active connections - --subtensor.network SUBTENSOR.NETWORK - The subtensor network flag. The likely choices are: -- finney (main network) -- local (local running network) -- - mock (creates a mock connection (for testing)) If this option is set it overloads subtensor.chain_endpoint with an - entry point node from that network. - --subtensor.chain_endpoint SUBTENSOR.CHAIN_ENDPOINT - The subtensor endpoint flag. If set, overrides the --network flag. - --subtensor._mock To turn on subtensor mocking for testing purposes. - --subtensor.register.num_processes SUBTENSOR.REGISTER.NUM_PROCESSES, -n SUBTENSOR.REGISTER.NUM_PROCESSES - Number of processors to use for registration - --subtensor.register.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, --subtensor.register.cuda.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, --cuda.update_interval SUBTENSOR.REGISTER.UPDATE_INTERVAL, -u SUBTENSOR.REGISTER.UPDATE_INTERVAL - The number of nonces to process before checking for next block during registration - --subtensor.register.no_output_in_place, --no_output_in_place - Whether to not ouput the registration statistics in-place. Set flag to disable output in-place. - --subtensor.register.verbose - Whether to ouput the registration statistics verbosely. - --subtensor.register.cuda.use_cuda, --cuda, --cuda.use_cuda - Set flag to use CUDA to register. - --subtensor.register.cuda.no_cuda, --no_cuda, --cuda.no_cuda - Set flag to not use CUDA for registration - --subtensor.register.cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...], --cuda.dev_id SUBTENSOR.REGISTER.CUDA.DEV_ID [SUBTENSOR.REGISTER.CUDA.DEV_ID ...] - Set the CUDA device id(s). Goes by the order of speed. (i.e. 0 is the fastest). - --subtensor.register.cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB, --cuda.TPB SUBTENSOR.REGISTER.CUDA.TPB - Set the number of Threads Per Block for CUDA. - --logging.debug Turn on bittensor debugging information - --logging.trace Turn on bittensor trace level information - --logging.record_log Turns on logging to file. - --logging.logging_dir LOGGING.LOGGING_DIR - Logging default root directory. - --metagraph._mock To turn on metagraph mocking for testing purposes. - --config CONFIG If set, defaults are overridden by passed file. - --strict If flagged, config will check that only exact arguemnts have been set. -``` diff --git a/neurons/text/prompting/miners/openai/neuron.py b/neurons/text/prompting/miners/openai/neuron.py deleted file mode 100644 index f41906cae9..0000000000 --- a/neurons/text/prompting/miners/openai/neuron.py +++ /dev/null @@ -1,110 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2023 Yuma Rao - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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 openai -import argparse -import bittensor -from typing import List, Dict - - -class OpenAIMiner(bittensor.BasePromptingMiner): - @classmethod - def check_config(cls, config: "bittensor.Config"): - assert ( - config.openai.api_key != None - ), "the miner requires passing --openai.api_key as an argument of the config." - - @classmethod - def add_args(cls, parser: argparse.ArgumentParser): - parser.add_argument("--openai.api_key", type=str, help="openai api key") - parser.add_argument( - "--openai.suffix", - type=str, - default=None, - help="The suffix that comes after a completion of inserted text.", - ) - parser.add_argument( - "--openai.max_tokens", - type=int, - default=256, - help="The maximum number of tokens to generate in the completion.", - ) - parser.add_argument( - "--openai.temperature", - type=float, - default=0.7, - help="Sampling temperature to use, between 0 and 2.", - ) - parser.add_argument( - "--openai.top_p", - type=float, - default=1, - help="Nucleus sampling parameter, top_p probability mass.", - ) - parser.add_argument( - "--openai.n", - type=int, - default=1, - help="How many completions to generate for each prompt.", - ) - parser.add_argument( - "--openai.presence_penalty", - type=float, - default=0, - help="Penalty for tokens based on their presence in the text so far.", - ) - parser.add_argument( - "--openai.frequency_penalty", - type=float, - default=0, - help="Penalty for tokens based on their frequency in the text so far.", - ) - parser.add_argument( - "--openai.model_name", - type=str, - default="gpt-3.5-turbo", - help="OpenAI model to use for completion.", - ) - - def backward( - self, messages: List[Dict[str, str]], response: str, rewards: torch.FloatTensor - ) -> str: - pass - - def __init__(self): - super(OpenAIMiner, self).__init__() - print(self.config) - openai.api_key = self.config.openai.api_key - - def forward(self, messages: List[Dict[str, str]]) -> str: - resp = openai.ChatCompletion.create( - model=self.config.openai.model_name, - messages=messages, - temperature=self.config.openai.temperature, - max_tokens=self.config.openai.max_tokens, - top_p=self.config.openai.top_p, - frequency_penalty=self.config.openai.frequency_penalty, - presence_penalty=self.config.openai.presence_penalty, - n=self.config.openai.n, - )["choices"][0]["message"]["content"] - return resp - - -if __name__ == "__main__": - bittensor.utils.version_checking() - OpenAIMiner().run() diff --git a/neurons/text/prompting/miners/openai/requirements.txt b/neurons/text/prompting/miners/openai/requirements.txt deleted file mode 100644 index f0dd0aec55..0000000000 --- a/neurons/text/prompting/miners/openai/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -openai \ No newline at end of file diff --git a/neurons/text/prompting/miners/self_hosted/__init__.py b/neurons/text/prompting/miners/self_hosted/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/neurons/text/prompting/miners/self_hosted/coati/__init__.py b/neurons/text/prompting/miners/self_hosted/coati/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/neurons/text/prompting/miners/self_hosted/coati/dataset/__init__.py b/neurons/text/prompting/miners/self_hosted/coati/dataset/__init__.py deleted file mode 100644 index 5b5129c49c..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/dataset/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -from .prompt_dataset import PromptDataset -from .reward_dataset import HhRlhfDataset, RmStaticDataset, SHPDataset -from .sft_dataset import DataCollatorForSupervisedDataset, SFTDataset, SupervisedDataset -from .utils import is_rank_0 - -__all__ = [ - "RmStaticDataset", - "HhRlhfDataset", - "SHPDataset", - "is_rank_0", - "SFTDataset", - "SupervisedDataset", - "DataCollatorForSupervisedDataset", - "PromptDataset", -] diff --git a/neurons/text/prompting/miners/self_hosted/coati/dataset/prompt_dataset.py b/neurons/text/prompting/miners/self_hosted/coati/dataset/prompt_dataset.py deleted file mode 100644 index f9502883bb..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/dataset/prompt_dataset.py +++ /dev/null @@ -1,53 +0,0 @@ -import copy -import random -from dataclasses import dataclass, field -from typing import Callable, Dict, Sequence - -import torch -import torch.distributed as dist -import transformers -from torch.utils.data import Dataset -from tqdm import tqdm - -from colossalai.logging import get_dist_logger - -from .utils import is_rank_0, jload - -logger = get_dist_logger() - - -class PromptDataset(Dataset): - """Dataset for supervised fine-tuning.""" - - def __init__( - self, - data_path: str, - tokenizer: transformers.PreTrainedTokenizer, - max_datasets_size: int = None, - ): - super(PromptDataset, self).__init__() - self.prompt = [] - logger.info("Loading data...") - list_data_dict = jload(data_path) - logger.info(f"Loaded {len(list_data_dict)} examples.") - - if max_datasets_size is not None: - logger.info(f"Limiting dataset to {max_datasets_size} examples.") - list_data_dict = list_data_dict[:max_datasets_size] - - for data_dict in list_data_dict: - token = tokenizer( - data_dict["instruction"], - return_tensors="pt", - max_length=96, - padding="max_length", - truncation=True, - ) - for idx in token["input_ids"]: - self.prompt.append(idx.to(torch.cuda.current_device())) - - def __len__(self): - return len(self.prompt) - - def __getitem__(self, i) -> Dict[str, torch.Tensor]: - return self.prompt[i] diff --git a/neurons/text/prompting/miners/self_hosted/coati/dataset/reward_dataset.py b/neurons/text/prompting/miners/self_hosted/coati/dataset/reward_dataset.py deleted file mode 100644 index 08d21cbc6b..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/dataset/reward_dataset.py +++ /dev/null @@ -1,208 +0,0 @@ -from typing import Callable - -from torch.utils.data import Dataset -from tqdm import tqdm - -from .utils import is_rank_0 - - -# Dahaos/rm-static -class RmStaticDataset(Dataset): - """ - Dataset for reward model - - Args: - dataset: dataset for reward model - tokenizer: tokenizer for reward model - max_length: max length of input - special_token: special token at the end of sentence - """ - - def __init__( - self, dataset, tokenizer: Callable, max_length: int, special_token=None - ) -> None: - super().__init__() - self.chosen = [] - self.reject = [] - if special_token is None: - self.end_token = tokenizer.eos_token - else: - self.end_token = special_token - for data in tqdm(dataset, disable=not is_rank_0()): - prompt = data["prompt"] - - chosen = prompt + data["chosen"] + self.end_token - chosen_token = tokenizer( - chosen, - max_length=max_length, - padding="max_length", - truncation=True, - return_tensors="pt", - ) - self.chosen.append( - { - "input_ids": chosen_token["input_ids"], - "attention_mask": chosen_token["attention_mask"], - } - ) - - reject = prompt + data["rejected"] + self.end_token - reject_token = tokenizer( - reject, - max_length=max_length, - padding="max_length", - truncation=True, - return_tensors="pt", - ) - self.reject.append( - { - "input_ids": reject_token["input_ids"], - "attention_mask": reject_token["attention_mask"], - } - ) - - def __len__(self): - length = len(self.chosen) - return length - - def __getitem__(self, idx): - return ( - self.chosen[idx]["input_ids"], - self.chosen[idx]["attention_mask"], - self.reject[idx]["input_ids"], - self.reject[idx]["attention_mask"], - ) - - -# Dahoas/filtered-SHP -class SHPDataset(Dataset): - """ - Dataset for reward model - - Args: - dataset: dataset for reward model - tokenizer: tokenizer for reward model - max_length: max length of input - special_token: special token at the end of sentence - """ - - def __init__( - self, dataset, tokenizer: Callable, max_length: int, special_token=None - ) -> None: - super().__init__() - self.chosen = [] - self.reject = [] - if special_token is None: - self.end_token = tokenizer.eos_token - else: - self.end_token = special_token - for data in tqdm(dataset, disable=not is_rank_0()): - prompt = data["prompt"] - - chosen = prompt + data["chosen"] + self.end_token - chosen_token = tokenizer( - chosen, - max_length=max_length, - padding="max_length", - truncation=True, - return_tensors="pt", - ) - self.chosen.append( - { - "input_ids": chosen_token["input_ids"], - "attention_mask": chosen_token["attention_mask"], - } - ) - - reject = prompt + data["rejected"] + self.end_token - reject_token = tokenizer( - reject, - max_length=max_length, - padding="max_length", - truncation=True, - return_tensors="pt", - ) - self.reject.append( - { - "input_ids": reject_token["input_ids"], - "attention_mask": reject_token["attention_mask"], - } - ) - - def __len__(self): - length = len(self.chosen) - return length - - def __getitem__(self, idx): - return ( - self.chosen[idx]["input_ids"], - self.chosen[idx]["attention_mask"], - self.reject[idx]["input_ids"], - self.reject[idx]["attention_mask"], - ) - - -# Anthropic/hh-rlhf -class HhRlhfDataset(Dataset): - """ - Dataset for reward model - - Args: - dataset: dataset for reward model - tokenizer: tokenizer for reward model - max_length: max length of input - special_token: special token at the end of sentence - """ - - def __init__( - self, dataset, tokenizer: Callable, max_length: int, special_token=None - ) -> None: - super().__init__() - self.chosen = [] - self.reject = [] - if special_token is None: - self.end_token = tokenizer.eos_token - else: - self.end_token = special_token - for data in tqdm(dataset, disable=not is_rank_0()): - chosen = data["chosen"] + self.end_token - chosen_token = tokenizer( - chosen, - max_length=max_length, - padding="max_length", - truncation=True, - return_tensors="pt", - ) - self.chosen.append( - { - "input_ids": chosen_token["input_ids"], - "attention_mask": chosen_token["attention_mask"], - } - ) - - reject = data["rejected"] + self.end_token - reject_token = tokenizer( - reject, - max_length=max_length, - padding="max_length", - truncation=True, - return_tensors="pt", - ) - self.reject.append( - { - "input_ids": reject_token["input_ids"], - "attention_mask": reject_token["attention_mask"], - } - ) - - def __len__(self): - length = len(self.chosen) - return length - - def __getitem__(self, idx): - return ( - self.chosen[idx]["input_ids"], - self.chosen[idx]["attention_mask"], - self.reject[idx]["input_ids"], - self.reject[idx]["attention_mask"], - ) diff --git a/neurons/text/prompting/miners/self_hosted/coati/dataset/sft_dataset.py b/neurons/text/prompting/miners/self_hosted/coati/dataset/sft_dataset.py deleted file mode 100644 index df7c9d2f7f..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/dataset/sft_dataset.py +++ /dev/null @@ -1,196 +0,0 @@ -# Copyright 2023 Rohan Taori, Ishaan Gulrajani, Tianyi Zhang, Yann Dubois, Xuechen Li -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import copy -import random -from dataclasses import dataclass, field -from typing import Callable, Dict, Sequence - -import torch -import torch.distributed as dist -import transformers -from torch.utils.data import Dataset -from tqdm import tqdm - -from colossalai.logging import get_dist_logger - -from .utils import is_rank_0, jload - -logger = get_dist_logger() - -IGNORE_INDEX = -100 -PROMPT_DICT = { - "prompt_input": ( - "Below is an instruction that describes a task, paired with an input that provides further context. " - "Write a response that appropriately completes the request.\n\n" - "### Instruction:\n{instruction}\n\n### Input:\n{input}\n\n### Response:" - ), - "prompt_no_input": ( - "Below is an instruction that describes a task. " - "Write a response that appropriately completes the request.\n\n" - "### Instruction:\n{instruction}\n\n### Response:" - ), -} - - -class SFTDataset(Dataset): - """ - Dataset for sft model - - Args: - dataset: dataset for supervised model - tokenizer: tokenizer for supervised model - max_length: max length of input - """ - - def __init__(self, dataset, tokenizer: Callable, max_length: int = 512) -> None: - super().__init__() - # self.prompts = [] - self.input_ids = [] - - for data in tqdm(dataset, disable=not is_rank_0()): - prompt = data["prompt"] + data["completion"] + "<|endoftext|>" - prompt_token = tokenizer( - prompt, - max_length=max_length, - padding="max_length", - truncation=True, - return_tensors="pt", - ) - - # self.prompts.append(prompt_token)s - self.input_ids.append(prompt_token) - self.labels = copy.deepcopy(self.input_ids) - - def __len__(self): - length = len(self.prompts) - return length - - def __getitem__(self, idx): - # dict(input_ids=self.input_ids[i], labels=self.labels[i]) - return dict(input_ids=self.input_ids[idx], labels=self.labels[idx]) - # return dict(self.prompts[idx], self.prompts[idx]) - - -def _tokenize_fn( - strings: Sequence[str], tokenizer: transformers.PreTrainedTokenizer -) -> Dict: - """Tokenize a list of strings.""" - tokenized_list = [ - tokenizer( - text, - return_tensors="pt", - padding="longest", - max_length=tokenizer.model_max_length, - truncation=True, - ) - for text in strings - ] - input_ids = labels = [tokenized.input_ids[0] for tokenized in tokenized_list] - input_ids_lens = labels_lens = [ - tokenized.input_ids.ne(tokenizer.pad_token_id).sum().item() - for tokenized in tokenized_list - ] - return dict( - input_ids=input_ids, - labels=labels, - input_ids_lens=input_ids_lens, - labels_lens=labels_lens, - ) - - -def preprocess( - sources: Sequence[str], - targets: Sequence[str], - tokenizer: transformers.PreTrainedTokenizer, -) -> Dict: - """Preprocess the data by tokenizing.""" - examples = [s + t for s, t in zip(sources, targets)] - examples_tokenized, sources_tokenized = [ - _tokenize_fn(strings, tokenizer) for strings in (examples, sources) - ] - input_ids = examples_tokenized["input_ids"] - labels = copy.deepcopy(input_ids) - for label, source_len in zip(labels, sources_tokenized["input_ids_lens"]): - label[:source_len] = IGNORE_INDEX - return dict(input_ids=input_ids, labels=labels) - - -class SupervisedDataset(Dataset): - """Dataset for supervised fine-tuning.""" - - def __init__( - self, - data_path: str, - tokenizer: transformers.PreTrainedTokenizer, - max_datasets_size: int = None, - ): - super(SupervisedDataset, self).__init__() - logger.info("Loading data...") - list_data_dict = jload(data_path) - logger.info(f"Loaded {len(list_data_dict)} examples.") - - if max_datasets_size is not None: - logger.info(f"Limiting dataset to {max_datasets_size} examples.") - list_data_dict = list_data_dict[:max_datasets_size] - - logger.info("Formatting inputs...") - prompt_input, prompt_no_input = ( - PROMPT_DICT["prompt_input"], - PROMPT_DICT["prompt_no_input"], - ) - sources = [ - prompt_input.format_map(example) - if example.get("input", "") != "" - else prompt_no_input.format_map(example) - for example in list_data_dict - ] - targets = [ - f"{example['output']}{tokenizer.eos_token}" for example in list_data_dict - ] - - logger.info("Tokenizing inputs... This may take some time...") - data_dict = preprocess(sources, targets, tokenizer) - - self.input_ids = data_dict["input_ids"] - self.labels = data_dict["labels"] - - def __len__(self): - return len(self.input_ids) - - def __getitem__(self, i) -> Dict[str, torch.Tensor]: - return dict(input_ids=self.input_ids[i], labels=self.labels[i]) - - -@dataclass -class DataCollatorForSupervisedDataset(object): - """Collate examples for supervised fine-tuning.""" - - tokenizer: transformers.PreTrainedTokenizer - - def __call__(self, instances: Sequence[Dict]) -> Dict[str, torch.Tensor]: - input_ids, labels = tuple( - [instance[key] for instance in instances] for key in ("input_ids", "labels") - ) - input_ids = torch.nn.utils.rnn.pad_sequence( - input_ids, batch_first=True, padding_value=self.tokenizer.pad_token_id - ) - labels = torch.nn.utils.rnn.pad_sequence( - labels, batch_first=True, padding_value=IGNORE_INDEX - ) - return dict( - input_ids=input_ids, - labels=labels, - attention_mask=input_ids.ne(self.tokenizer.pad_token_id), - ) diff --git a/neurons/text/prompting/miners/self_hosted/coati/dataset/utils.py b/neurons/text/prompting/miners/self_hosted/coati/dataset/utils.py deleted file mode 100644 index f37fce67a7..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/dataset/utils.py +++ /dev/null @@ -1,22 +0,0 @@ -import io -import json - -import torch.distributed as dist - - -def is_rank_0() -> bool: - return not dist.is_initialized() or dist.get_rank() == 0 - - -def _make_r_io_base(f, mode: str): - if not isinstance(f, io.IOBase): - f = open(f, mode=mode) - return f - - -def jload(f, mode="r"): - """Load a .json file into a dictionary.""" - f = _make_r_io_base(f, mode) - jdict = json.load(f) - f.close() - return jdict diff --git a/neurons/text/prompting/miners/self_hosted/coati/experience_maker/__init__.py b/neurons/text/prompting/miners/self_hosted/coati/experience_maker/__init__.py deleted file mode 100644 index 06452292e7..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/experience_maker/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from .base import Experience, ExperienceMaker -from .naive import NaiveExperienceMaker - -__all__ = ["Experience", "ExperienceMaker", "NaiveExperienceMaker"] diff --git a/neurons/text/prompting/miners/self_hosted/coati/experience_maker/base.py b/neurons/text/prompting/miners/self_hosted/coati/experience_maker/base.py deleted file mode 100644 index 80d4d09db8..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/experience_maker/base.py +++ /dev/null @@ -1,79 +0,0 @@ -from abc import ABC, abstractmethod -from dataclasses import dataclass -from typing import Optional - -import torch -import torch.nn as nn -from coati.models.base import Actor - - -@dataclass -class Experience: - """Experience is a batch of data. - These data should have the the sequence length and number of actions. - Left padding for sequences is applied. - - Shapes of each tensor: - sequences: (B, S) - action_log_probs: (B, A) - values: (B) - reward: (B) - advatanges: (B) - attention_mask: (B, S) - action_mask: (B, A) - - "A" is the number of actions. - """ - - sequences: torch.Tensor - action_log_probs: torch.Tensor - values: torch.Tensor - reward: torch.Tensor - advantages: torch.Tensor - attention_mask: Optional[torch.LongTensor] - action_mask: Optional[torch.BoolTensor] - - @torch.no_grad() - def to_device(self, device: torch.device) -> None: - self.sequences = self.sequences.to(device) - self.action_log_probs = self.action_log_probs.to(device) - self.values = self.values.to(device) - self.reward = self.reward.to(device) - self.advantages = self.advantages.to(device) - if self.attention_mask is not None: - self.attention_mask = self.attention_mask.to(device) - if self.action_mask is not None: - self.action_mask = self.action_mask.to(device) - - def pin_memory(self): - self.sequences = self.sequences.pin_memory() - self.action_log_probs = self.action_log_probs.pin_memory() - self.values = self.values.pin_memory() - self.reward = self.reward.pin_memory() - self.advantages = self.advantages.pin_memory() - if self.attention_mask is not None: - self.attention_mask = self.attention_mask.pin_memory() - if self.action_mask is not None: - self.action_mask = self.action_mask.pin_memory() - return self - - -class ExperienceMaker(ABC): - def __init__( - self, - actor: Actor, - critic: nn.Module, - reward_model: nn.Module, - initial_model: Actor, - kl_coef: float = 0.1, - ) -> None: - super().__init__() - self.actor = actor - self.critic = critic - self.reward_model = reward_model - self.initial_model = initial_model - self.kl_coef = kl_coef - - @abstractmethod - def make_experience(self, input_ids: torch.Tensor, **generate_kwargs) -> Experience: - pass diff --git a/neurons/text/prompting/miners/self_hosted/coati/experience_maker/naive.py b/neurons/text/prompting/miners/self_hosted/coati/experience_maker/naive.py deleted file mode 100644 index 9cea324268..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/experience_maker/naive.py +++ /dev/null @@ -1,51 +0,0 @@ -import torch -from coati.models.utils import compute_reward, normalize - -from .base import Experience, ExperienceMaker - - -class NaiveExperienceMaker(ExperienceMaker): - """ - Naive experience maker. - """ - - @torch.no_grad() - def make_experience(self, input_ids: torch.Tensor, **generate_kwargs) -> Experience: - self.actor.eval() - self.critic.eval() - self.initial_model.eval() - self.reward_model.eval() - - sequences, attention_mask, action_mask = self.actor.generate( - input_ids, return_action_mask=True, **generate_kwargs - ) - num_actions = action_mask.size(1) - - action_log_probs = self.actor(sequences, num_actions, attention_mask) - base_action_log_probs = self.initial_model( - sequences, num_actions, attention_mask - ) - value = self.critic(sequences, action_mask, attention_mask) - r = self.reward_model(sequences, attention_mask) - reward = compute_reward( - r, - self.kl_coef, - action_log_probs, - base_action_log_probs, - action_mask=action_mask, - ) - - advantage = reward - value - # TODO(ver217): maybe normalize adv - if advantage.ndim == 1: - advantage = advantage.unsqueeze(-1) - - return Experience( - sequences, - action_log_probs, - value, - reward, - advantage, - attention_mask, - action_mask, - ) diff --git a/neurons/text/prompting/miners/self_hosted/coati/models/__init__.py b/neurons/text/prompting/miners/self_hosted/coati/models/__init__.py deleted file mode 100644 index ff66d373b0..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/models/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -from .base import Actor, Critic, RewardModel -from .loss import LogExpLoss, LogSigLoss, PolicyLoss, PPOPtxActorLoss, ValueLoss - -__all__ = [ - "Actor", - "Critic", - "RewardModel", - "PolicyLoss", - "ValueLoss", - "PPOPtxActorLoss", - "LogSigLoss", - "LogExpLoss", -] diff --git a/neurons/text/prompting/miners/self_hosted/coati/models/auto/__init__.py b/neurons/text/prompting/miners/self_hosted/coati/models/auto/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/neurons/text/prompting/miners/self_hosted/coati/models/auto/actor.py b/neurons/text/prompting/miners/self_hosted/coati/models/auto/actor.py deleted file mode 100644 index 8da4d94109..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/models/auto/actor.py +++ /dev/null @@ -1,37 +0,0 @@ -from typing import Optional - -from transformers import AutoConfig, AutoModelForCausalLM - - -from base import Actor - - -class AutoActor(Actor): - """ - Auto Actor model. - - Args: - pretrained (str): Pretrained model name or path. - config (AutoConfig): Model config. - checkpoint (bool): Enable gradient checkpointing. - lora_rank (int): Rank of the low-rank approximation. - lora_train_bias (str): LoRA bias training mode. - """ - - def __init__( - self, - pretrained: Optional[str] = None, - config: Optional[AutoConfig] = None, - checkpoint: bool = False, - lora_rank: int = 0, - lora_train_bias: str = "none", - ) -> None: - if pretrained is not None: - model = AutoModelForCausalLM.from_pretrained(pretrained) - elif config is not None: - model = AutoModelForCausalLM(config) - else: - model = AutoModelForCausalLM(AutoConfig()) - if checkpoint: - model.gradient_checkpointing_enable() - super().__init__(model, lora_rank, lora_train_bias) diff --git a/neurons/text/prompting/miners/self_hosted/coati/models/auto/critic.py b/neurons/text/prompting/miners/self_hosted/coati/models/auto/critic.py deleted file mode 100644 index e5a61de83b..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/models/auto/critic.py +++ /dev/null @@ -1,38 +0,0 @@ -from typing import Optional - -import torch.nn as nn -from transformers import AutoConfig, AutoModel -from ..base import Critic - - -class AutoCritic(Critic): - """ - Auto Critic model. - - Args: - pretrained (str): Pretrained model name or path. - config (AutoConfig): Model config. - checkpoint (bool): Enable gradient checkpointing. - lora_rank (int): Rank of the low-rank approximation. - lora_train_bias (str): LoRA bias training mode. - """ - - def __init__( - self, - pretrained: Optional[str] = None, - config: Optional[AutoConfig] = None, - checkpoint: bool = False, - lora_rank: int = 0, - lora_train_bias: str = "none", - **kwargs - ) -> None: - if pretrained is not None: - model = AutoModel.from_pretrained(pretrained) - elif config is not None: - model = AutoModel(config) - else: - model = AutoModel(AutoConfig()) - if checkpoint: - model.gradient_checkpointing_enable() - value_head = nn.Linear(model.config.word_embed_proj_dim, 1) - super().__init__(model, value_head, lora_rank, lora_train_bias, **kwargs) diff --git a/neurons/text/prompting/miners/self_hosted/coati/models/auto/lm.py b/neurons/text/prompting/miners/self_hosted/coati/models/auto/lm.py deleted file mode 100644 index d88022304e..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/models/auto/lm.py +++ /dev/null @@ -1,40 +0,0 @@ -from typing import Optional - -from transformers import AutoConfig, AutoModelForCausalLM -from ..base import LM - - -class AutoLM(LM): - """ - Auto language model. - - Args: - pretrained (str): Pretrained model name or path. - config (AutoConfig): Model config. - checkpoint (bool): Enable gradient checkpointing. - lora_rank (int): Rank of the low-rank approximation. - lora_train_bias (str): LoRA bias training mode. - """ - - def __init__( - self, - pretrained: Optional[str] = None, - config: Optional[AutoConfig] = None, - checkpoint: bool = False, - lora_rank: int = 0, - lora_train_bias: str = "none", - ) -> None: - if pretrained is not None: - model = AutoModelForCausalLM.from_pretrained(pretrained) - elif config is not None: - model = AutoModelForCausalLM(config) - else: - model = AutoModelForCausalLM(AutoConfig()) - if checkpoint: - model.gradient_checkpointing_enable() - super().__init__(model, lora_rank, lora_train_bias) - - def forward(self, input_ids, attention_mask=None, labels=None, **kwargs): - return self.model( - input_ids, attention_mask=attention_mask, labels=labels, **kwargs - ) diff --git a/neurons/text/prompting/miners/self_hosted/coati/models/auto/reward_model.py b/neurons/text/prompting/miners/self_hosted/coati/models/auto/reward_model.py deleted file mode 100644 index 1af9e34afc..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/models/auto/reward_model.py +++ /dev/null @@ -1,41 +0,0 @@ -from typing import Optional - -import torch.nn as nn -from transformers import AutoConfig, AutoModel -from ..base import RewardModel - - -class AutoRM(RewardModel): - """ - Auto Reward model. - - Args: - pretrained (str): Pretrained model name or path. - config (AutoConfig): Model config. - checkpoint (bool): Enable gradient checkpointing. - lora_rank (int): Rank of the low-rank approximation. - lora_train_bias (str): LoRA bias training mode. - """ - - def __init__( - self, - pretrained: Optional[str] = None, - config: Optional[AutoConfig] = None, - checkpoint: bool = False, - lora_rank: int = 0, - lora_train_bias: str = "none", - ) -> None: - if pretrained is not None: - model = AutoModel.from_pretrained(pretrained) - elif config is not None: - model = AutoModel(config) - else: - model = AutoModel(AutoConfig()) - if checkpoint: - model.gradient_checkpointing_enable() - - value_head = nn.Linear(model.config.word_embed_proj_dim, 1) - value_head.weight.data.normal_( - mean=0.0, std=1 / (model.config.word_embed_proj_dim + 1) - ) - super().__init__(model, value_head, lora_rank, lora_train_bias) diff --git a/neurons/text/prompting/miners/self_hosted/coati/models/base/__init__.py b/neurons/text/prompting/miners/self_hosted/coati/models/base/__init__.py deleted file mode 100644 index 6d20f10336..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/models/base/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from .actor import Actor -from .critic import Critic -from .lm import LM -from .reward_model import RewardModel - -__all__ = ["Actor", "Critic", "RewardModel", "LM"] diff --git a/neurons/text/prompting/miners/self_hosted/coati/models/base/actor.py b/neurons/text/prompting/miners/self_hosted/coati/models/base/actor.py deleted file mode 100644 index 6189173de6..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/models/base/actor.py +++ /dev/null @@ -1,76 +0,0 @@ -from typing import Optional, Tuple, Union - -import torch -import torch.nn as nn -import torch.nn.functional as F - -from ..generation import generate -from ..lora import LoRAModule -from ..utils import log_probs_from_logits - - -class Actor(LoRAModule): - """ - Actor model base class. - - Args: - model (nn.Module): Actor Model. - lora_rank (int): LoRA rank. - lora_train_bias (str): LoRA bias training mode. - """ - - def __init__( - self, model: nn.Module, lora_rank: int = 0, lora_train_bias: str = "none" - ) -> None: - super().__init__(lora_rank=lora_rank, lora_train_bias=lora_train_bias) - self.model = model - self.convert_to_lora() - - @torch.no_grad() - def generate( - self, input_ids: torch.Tensor, return_action_mask: bool = True, **kwargs - ) -> Union[ - Tuple[torch.LongTensor, torch.LongTensor], - Tuple[torch.LongTensor, torch.LongTensor, torch.BoolTensor], - ]: - sequences = generate(self.model, input_ids, **kwargs) - attention_mask = None - pad_token_id = kwargs.get("pad_token_id", None) - if pad_token_id is not None: - attention_mask = sequences.not_equal(pad_token_id).to( - dtype=torch.long, device=sequences.device - ) - if not return_action_mask: - return sequences, attention_mask, None - input_len = input_ids.size(1) - eos_token_id = kwargs.get("eos_token_id", None) - if eos_token_id is None: - action_mask = torch.ones_like(sequences, dtype=torch.bool) - else: - # left padding may be applied, only mask action - action_mask = (sequences[:, input_len:] == eos_token_id).cumsum(dim=-1) == 0 - action_mask = F.pad( - action_mask, (1 + input_len, -1), value=True - ) # include eos token and input - action_mask[:, :input_len] = False - action_mask = action_mask[:, 1:] - return ( - sequences, - attention_mask, - action_mask[:, -(sequences.size(1) - input_len) :], - ) - - def forward( - self, - sequences: torch.LongTensor, - num_actions: int, - attention_mask: Optional[torch.Tensor] = None, - ) -> torch.Tensor: - """Returns action log probs""" - output = self.model(sequences, attention_mask=attention_mask) - logits = output["logits"] - log_probs = log_probs_from_logits(logits[:, :-1, :], sequences[:, 1:]) - return log_probs[:, -num_actions:] - - def get_base_model(self): - return self.model diff --git a/neurons/text/prompting/miners/self_hosted/coati/models/base/critic.py b/neurons/text/prompting/miners/self_hosted/coati/models/base/critic.py deleted file mode 100644 index 54ab7fa47d..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/models/base/critic.py +++ /dev/null @@ -1,55 +0,0 @@ -from typing import Optional - -import torch -import torch.nn as nn - -from ..lora import LoRAModule -from ..utils import masked_mean - - -class Critic(LoRAModule): - """ - Critic model base class. - - Args: - model (nn.Module): Critic model. - value_head (nn.Module): Value head to get value. - lora_rank (int): LoRA rank. - lora_train_bias (str): LoRA bias training mode. - """ - - def __init__( - self, - model: nn.Module, - value_head: nn.Module, - lora_rank: int = 0, - lora_train_bias: str = "none", - use_action_mask: bool = False, - ) -> None: - super().__init__(lora_rank=lora_rank, lora_train_bias=lora_train_bias) - self.model = model - self.value_head = value_head - self.use_action_mask = use_action_mask - self.convert_to_lora() - - def forward( - self, - sequences: torch.LongTensor, - action_mask: Optional[torch.Tensor] = None, - attention_mask: Optional[torch.Tensor] = None, - ) -> torch.Tensor: - outputs = self.model(sequences, attention_mask=attention_mask) - last_hidden_states = outputs["last_hidden_state"] - - values = self.value_head(last_hidden_states).squeeze(-1) - - if action_mask is not None and self.use_action_mask: - num_actions = action_mask.size(1) - prompt_mask = attention_mask[:, :-num_actions] - values = values[:, :-num_actions] - value = masked_mean(values, prompt_mask, dim=1) - return value - - values = values[:, :-1] - value = values.mean(dim=1) - return value diff --git a/neurons/text/prompting/miners/self_hosted/coati/models/base/lm.py b/neurons/text/prompting/miners/self_hosted/coati/models/base/lm.py deleted file mode 100644 index d99c3e1e46..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/models/base/lm.py +++ /dev/null @@ -1,35 +0,0 @@ -from typing import Optional, Tuple, Union - -import torch -import torch.nn as nn -import torch.nn.functional as F - -from ..generation import generate -from .actor import Actor - - -class LM(Actor): - """ - Language model base class. - - Args: - model (nn.Module): Language Model. - lora_rank (int): LoRA rank. - lora_train_bias (str): LoRA bias training mode. - """ - - def __init__( - self, model: nn.Module, lora_rank: int = 0, lora_train_bias: str = "none" - ) -> None: - super().__init__( - model=model, lora_rank=lora_rank, lora_train_bias=lora_train_bias - ) - - def forward( - self, sequences: torch.LongTensor, attention_mask: Optional[torch.Tensor] = None - ) -> torch.Tensor: - """Returns output log probs""" - output = self.model(sequences, attention_mask=attention_mask) - logits = output["logits"] - log_probs = F.log_softmax(logits, dim=-1) - return log_probs diff --git a/neurons/text/prompting/miners/self_hosted/coati/models/base/reward_model.py b/neurons/text/prompting/miners/self_hosted/coati/models/base/reward_model.py deleted file mode 100644 index 350b58401f..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/models/base/reward_model.py +++ /dev/null @@ -1,47 +0,0 @@ -from typing import Optional - -import torch -import torch.nn as nn - -from ..lora import LoRAModule - - -class RewardModel(LoRAModule): - """ - Reward model base class. - - Args: - model (nn.Module): Reward model. - value_head (nn.Module): Value head to get reward score. - lora_rank (int): LoRA rank. - lora_train_bias (str): LoRA bias training mode. - """ - - def __init__( - self, - model: nn.Module, - value_head: Optional[nn.Module] = None, - lora_rank: int = 0, - lora_train_bias: str = "none", - ) -> None: - super().__init__(lora_rank=lora_rank, lora_train_bias=lora_train_bias) - self.model = model - self.convert_to_lora() - - if value_head is not None: - if value_head.out_features != 1: - raise ValueError( - "The value head of reward model's output dim should be 1!" - ) - self.value_head = value_head - else: - self.value_head = nn.Linear(model.config.n_embd, 1) - - def forward( - self, sequences: torch.LongTensor, attention_mask: Optional[torch.Tensor] = None - ) -> torch.Tensor: - outputs = self.model(sequences, attention_mask=attention_mask) - last_hidden_states = outputs["last_hidden_state"] - values = self.value_head(last_hidden_states)[:, :-1] - value = values.mean(dim=1).squeeze(1) # ensure shape is (B) - return value diff --git a/neurons/text/prompting/miners/self_hosted/coati/models/bloom/__init__.py b/neurons/text/prompting/miners/self_hosted/coati/models/bloom/__init__.py deleted file mode 100644 index 0526a3e8cc..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/models/bloom/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from .bloom_actor import BLOOMActor -from .bloom_critic import BLOOMCritic -from .bloom_lm import BLOOMLM -from .bloom_rm import BLOOMRM - -__all__ = ["BLOOMActor", "BLOOMCritic", "BLOOMRM", "BLOOMLM"] diff --git a/neurons/text/prompting/miners/self_hosted/coati/models/bloom/bloom_actor.py b/neurons/text/prompting/miners/self_hosted/coati/models/bloom/bloom_actor.py deleted file mode 100644 index 32b04b18de..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/models/bloom/bloom_actor.py +++ /dev/null @@ -1,37 +0,0 @@ -from typing import Optional - -import torch -from transformers import BloomConfig, BloomForCausalLM, BloomModel - -from ..base import Actor - - -class BLOOMActor(Actor): - """ - BLOOM Actor model. - - Args: - pretrained (str): Pretrained model name or path. - config (BloomConfig): Model config. - checkpoint (bool): Enable gradient checkpointing. - lora_rank (int): LoRA rank. - lora_train_bias (str): LoRA bias training mode. - """ - - def __init__( - self, - pretrained: str = None, - config: Optional[BloomConfig] = None, - checkpoint: bool = False, - lora_rank: int = 0, - lora_train_bias: str = "none", - ) -> None: - if pretrained is not None: - model = BloomForCausalLM.from_pretrained(pretrained) - elif config is not None: - model = BloomForCausalLM(config) - else: - model = BloomForCausalLM(BloomConfig()) - if checkpoint: - model.gradient_checkpointing_enable() - super().__init__(model, lora_rank, lora_train_bias) diff --git a/neurons/text/prompting/miners/self_hosted/coati/models/bloom/bloom_critic.py b/neurons/text/prompting/miners/self_hosted/coati/models/bloom/bloom_critic.py deleted file mode 100644 index 8ed0643814..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/models/bloom/bloom_critic.py +++ /dev/null @@ -1,40 +0,0 @@ -from typing import Optional - -import torch -import torch.nn as nn -from transformers import BloomConfig, BloomForCausalLM, BloomModel - -from ..base import Critic - - -class BLOOMCritic(Critic): - """ - BLOOM Critic model. - - Args: - pretrained (str): Pretrained model name or path. - config (BloomConfig): Model config. - checkpoint (bool): Enable gradient checkpointing. - lora_rank (int): LoRA rank. - lora_train_bias (str): LoRA bias training mode. - """ - - def __init__( - self, - pretrained: str = None, - config: Optional[BloomConfig] = None, - checkpoint: bool = False, - lora_rank: int = 0, - lora_train_bias: str = "none", - **kwargs - ) -> None: - if pretrained is not None: - model = BloomModel.from_pretrained(pretrained) - elif config is not None: - model = BloomModel(config) - else: - model = BloomModel(BloomConfig()) - if checkpoint: - model.gradient_checkpointing_enable() - value_head = nn.Linear(model.config.hidden_size, 1) - super().__init__(model, value_head, lora_rank, lora_train_bias, **kwargs) diff --git a/neurons/text/prompting/miners/self_hosted/coati/models/bloom/bloom_lm.py b/neurons/text/prompting/miners/self_hosted/coati/models/bloom/bloom_lm.py deleted file mode 100644 index 83daa08875..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/models/bloom/bloom_lm.py +++ /dev/null @@ -1,42 +0,0 @@ -from typing import Optional - -import torch -from transformers import BloomConfig, BloomForCausalLM, BloomModel - -from ..base import LM - - -class BLOOMLM(LM): - """ - BLOOM language model. - - Args: - pretrained (str): Pretrained model name or path. - config (BloomConfig): Model config. - checkpoint (bool): Enable gradient checkpointing. - lora_rank (int): LoRA rank. - lora_train_bias (str): LoRA bias training mode. - """ - - def __init__( - self, - pretrained: str = None, - config: Optional[BloomConfig] = None, - checkpoint: bool = False, - lora_rank: int = 0, - lora_train_bias: str = "none", - ) -> None: - if pretrained is not None: - model = BloomForCausalLM.from_pretrained(pretrained) - elif config is not None: - model = BloomForCausalLM(config) - else: - model = BloomForCausalLM(BloomConfig()) - if checkpoint: - model.gradient_checkpointing_enable() - super().__init__(model, lora_rank, lora_train_bias) - - def forward(self, input_ids, attention_mask=None, labels=None, **kwargs): - return self.model( - input_ids, attention_mask=attention_mask, labels=labels, **kwargs - ) diff --git a/neurons/text/prompting/miners/self_hosted/coati/models/bloom/bloom_rm.py b/neurons/text/prompting/miners/self_hosted/coati/models/bloom/bloom_rm.py deleted file mode 100644 index ac28fbdf68..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/models/bloom/bloom_rm.py +++ /dev/null @@ -1,39 +0,0 @@ -from typing import Optional - -import torch.nn as nn -from transformers import BloomConfig, BloomForCausalLM, BloomModel - -from ..base import RewardModel - - -class BLOOMRM(RewardModel): - """ - BLOOM Reward model. - - Args: - pretrained (str): Pretrained model name or path. - config (BloomConfig): Model config. - checkpoint (bool): Enable gradient checkpointing. - lora_rank (int): LoRA rank. - lora_train_bias (str): LoRA bias training mode. - """ - - def __init__( - self, - pretrained: str = None, - config: Optional[BloomConfig] = None, - checkpoint: bool = False, - lora_rank: int = 0, - lora_train_bias: str = "none", - ) -> None: - if pretrained is not None: - model = BloomModel.from_pretrained(pretrained) - elif config is not None: - model = BloomModel(config) - else: - model = BloomModel(BloomConfig()) - if checkpoint: - model.gradient_checkpointing_enable() - value_head = nn.Linear(model.config.hidden_size, 1) - value_head.weight.data.normal_(mean=0.0, std=1 / (model.config.hidden_size + 1)) - super().__init__(model, value_head, lora_rank, lora_train_bias) diff --git a/neurons/text/prompting/miners/self_hosted/coati/models/deberta/__init__.py b/neurons/text/prompting/miners/self_hosted/coati/models/deberta/__init__.py deleted file mode 100644 index 1d93b5aa0f..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/models/deberta/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from .deberta_critic import DebertaCritic -from .deberta_rm import DebertaRM - -__all__ = ["DebertaCritic", "DebertaRM"] diff --git a/neurons/text/prompting/miners/self_hosted/coati/models/deberta/deberta_critic.py b/neurons/text/prompting/miners/self_hosted/coati/models/deberta/deberta_critic.py deleted file mode 100644 index a23ac1651b..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/models/deberta/deberta_critic.py +++ /dev/null @@ -1,38 +0,0 @@ -from typing import Optional - -import torch.nn as nn -from transformers import DebertaV2Config, DebertaV2Model - -from ..base import Critic - - -class DebertaCritic(Critic): - """ - Deberta Critic model. - - Args: - pretrained (str): Pretrained model name or path. - config (DebertaV2Config): Model config. - checkpoint (bool): Enable gradient checkpointing. - lora_rank (int): Rank of the LO-RA decomposition. - lora_train_bias (str): LoRA bias training mode. - """ - - def __init__( - self, - pretrained: Optional[str] = None, - config: Optional[DebertaV2Config] = None, - checkpoint: bool = False, - lora_rank: int = 0, - lora_train_bias: str = "none", - ) -> None: - if pretrained is not None: - model = DebertaV2Model.from_pretrained(pretrained) - elif config is not None: - model = DebertaV2Model(config) - else: - model = DebertaV2Model(DebertaV2Config()) - if checkpoint: - model.gradient_checkpointing_enable() - value_head = nn.Linear(model.config.hidden_size, 1) - super().__init__(model, value_head, lora_rank, lora_train_bias) diff --git a/neurons/text/prompting/miners/self_hosted/coati/models/deberta/deberta_rm.py b/neurons/text/prompting/miners/self_hosted/coati/models/deberta/deberta_rm.py deleted file mode 100644 index cc8be374af..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/models/deberta/deberta_rm.py +++ /dev/null @@ -1,39 +0,0 @@ -from typing import Optional - -import torch.nn as nn -from transformers import DebertaV2Config, DebertaV2Model - -from ..base import RewardModel - - -class DebertaRM(RewardModel): - """ - Deberta Reward model. - - Args: - pretrained (str): Pretrained model name or path. - config (DebertaV2Config): Model config. - checkpoint (bool): Enable gradient checkpointing. - lora_rank (int): Rank of the LO-RA decomposition. - lora_train_bias (str): LoRA bias training mode. - """ - - def __init__( - self, - pretrained: str = None, - config: Optional[DebertaV2Config] = None, - checkpoint: bool = False, - lora_rank: int = 0, - lora_train_bias: str = "none", - ) -> None: - if pretrained is not None: - model = DebertaV2Model.from_pretrained(pretrained) - elif config is not None: - model = DebertaV2Model(config) - else: - model = DebertaV2Model(DebertaV2Config()) - if checkpoint: - model.gradient_checkpointing_enable() - value_head = nn.Linear(model.config.hidden_size, 1) - value_head.weight.data.normal_(mean=0.0, std=1 / (model.config.hidden_size + 1)) - super().__init__(model, value_head, lora_rank, lora_train_bias) diff --git a/neurons/text/prompting/miners/self_hosted/coati/models/generation.py b/neurons/text/prompting/miners/self_hosted/coati/models/generation.py deleted file mode 100644 index f0388cddcd..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/models/generation.py +++ /dev/null @@ -1,167 +0,0 @@ -from typing import Any, Callable, Optional - -import torch -import torch.distributed as dist -import torch.nn as nn - -try: - from transformers.generation_logits_process import ( - LogitsProcessorList, - TemperatureLogitsWarper, - TopKLogitsWarper, - TopPLogitsWarper, - ) -except ImportError: - from transformers.generation import ( - LogitsProcessorList, - TemperatureLogitsWarper, - TopKLogitsWarper, - TopPLogitsWarper, - ) - - -def prepare_logits_processor( - top_k: Optional[int] = None, - top_p: Optional[float] = None, - temperature: Optional[float] = None, -) -> LogitsProcessorList: - processor_list = LogitsProcessorList() - if temperature is not None and temperature != 1.0: - processor_list.append(TemperatureLogitsWarper(temperature)) - if top_k is not None and top_k != 0: - processor_list.append(TopKLogitsWarper(top_k)) - if top_p is not None and top_p < 1.0: - processor_list.append(TopPLogitsWarper(top_p)) - return processor_list - - -def _is_sequence_finished(unfinished_sequences: torch.Tensor) -> bool: - if dist.is_initialized() and dist.get_world_size() > 1: - # consider DP - unfinished_sequences = unfinished_sequences.clone() - dist.all_reduce(unfinished_sequences) - return unfinished_sequences.max() == 0 - - -def sample( - model: nn.Module, - input_ids: torch.Tensor, - max_length: int, - early_stopping: bool = False, - eos_token_id: Optional[int] = None, - pad_token_id: Optional[int] = None, - top_k: Optional[int] = None, - top_p: Optional[float] = None, - temperature: Optional[float] = None, - prepare_inputs_fn: Optional[Callable[[torch.Tensor, Any], dict]] = None, - update_model_kwargs_fn: Optional[Callable[[dict, Any], dict]] = None, - **model_kwargs -) -> torch.Tensor: - if input_ids.size(1) >= max_length: - return input_ids - - logits_processor = prepare_logits_processor(top_k, top_p, temperature) - unfinished_sequences = input_ids.new(input_ids.shape[0]).fill_(1) - - for _ in range(input_ids.size(1), max_length): - model_inputs = ( - prepare_inputs_fn(input_ids, **model_kwargs) - if prepare_inputs_fn is not None - else {"input_ids": input_ids} - ) - outputs = model(**model_inputs) - - next_token_logits = outputs["logits"][:, -1, :] - # pre-process distribution - next_token_logits = logits_processor(input_ids, next_token_logits) - # sample - probs = torch.softmax(next_token_logits, dim=-1, dtype=torch.float) - next_tokens = torch.multinomial(probs, num_samples=1).squeeze(1) - - # finished sentences should have their next token be a padding token - if eos_token_id is not None: - if pad_token_id is None: - raise ValueError( - "If `eos_token_id` is defined, make sure that `pad_token_id` is defined." - ) - next_tokens = next_tokens * unfinished_sequences + pad_token_id * ( - 1 - unfinished_sequences - ) - - # update generated ids, model inputs for next step - input_ids = torch.cat([input_ids, next_tokens[:, None]], dim=-1) - if update_model_kwargs_fn is not None: - model_kwargs = update_model_kwargs_fn(outputs, **model_kwargs) - - # if eos_token was found in one sentence, set sentence to finished - if eos_token_id is not None: - unfinished_sequences = unfinished_sequences.mul( - (next_tokens != eos_token_id).long() - ) - - # stop when each sentence is finished if early_stopping=True - if early_stopping and _is_sequence_finished(unfinished_sequences): - break - - return input_ids - - -def generate( - model: nn.Module, - input_ids: torch.Tensor, - max_length: int, - num_beams: int = 1, - do_sample: bool = True, - early_stopping: bool = False, - eos_token_id: Optional[int] = None, - pad_token_id: Optional[int] = None, - top_k: Optional[int] = None, - top_p: Optional[float] = None, - temperature: Optional[float] = None, - prepare_inputs_fn: Optional[Callable[[torch.Tensor, Any], dict]] = None, - update_model_kwargs_fn: Optional[Callable[[dict, Any], dict]] = None, - **model_kwargs -) -> torch.Tensor: - """Generate token sequence. The returned sequence is input_ids + generated_tokens. - - Args: - model (nn.Module): model - input_ids (torch.Tensor): input sequence - max_length (int): max length of the returned sequence - num_beams (int, optional): number of beams. Defaults to 1. - do_sample (bool, optional): whether to do sample. Defaults to True. - early_stopping (bool, optional): if True, the sequence length may be smaller than max_length due to finding eos. Defaults to False. - eos_token_id (Optional[int], optional): end of sequence token id. Defaults to None. - pad_token_id (Optional[int], optional): pad token id. Defaults to None. - top_k (Optional[int], optional): the number of highest probability vocabulary tokens to keep for top-k-filtering. Defaults to None. - top_p (Optional[float], optional): If set to float < 1, only the smallest set of most probable tokens with probabilities that add up to top_p or higher are kept for generation. Defaults to None. - temperature (Optional[float], optional): The value used to module the next token probabilities. Defaults to None. - prepare_inputs_fn (Optional[Callable[[torch.Tensor, Any], dict]], optional): Function to preprocess model inputs. Arguments of this function should be input_ids and model_kwargs. Defaults to None. - update_model_kwargs_fn (Optional[Callable[[dict, Any], dict]], optional): Function to update model_kwargs based on outputs. Arguments of this function should be outputs and model_kwargs. Defaults to None. - """ - is_greedy_gen_mode = (num_beams == 1) and do_sample is False - is_sample_gen_mode = (num_beams == 1) and do_sample is True - is_beam_gen_mode = (num_beams > 1) and do_sample is False - if is_greedy_gen_mode: - # run greedy search - raise NotImplementedError - elif is_sample_gen_mode: - # run sample - return sample( - model, - input_ids, - max_length, - early_stopping=early_stopping, - eos_token_id=eos_token_id, - pad_token_id=pad_token_id, - top_k=top_k, - top_p=top_p, - temperature=temperature, - prepare_inputs_fn=prepare_inputs_fn, - update_model_kwargs_fn=update_model_kwargs_fn, - **model_kwargs - ) - elif is_beam_gen_mode: - raise NotImplementedError - else: - raise ValueError("Unsupported generation mode") diff --git a/neurons/text/prompting/miners/self_hosted/coati/models/generation_utils.py b/neurons/text/prompting/miners/self_hosted/coati/models/generation_utils.py deleted file mode 100644 index b64d1d0fe1..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/models/generation_utils.py +++ /dev/null @@ -1,102 +0,0 @@ -from typing import Optional - -import torch - - -def gpt_prepare_inputs_fn( - input_ids: torch.Tensor, past: Optional[torch.Tensor] = None, **kwargs -) -> dict: - token_type_ids = kwargs.get("token_type_ids", None) - # only last token for inputs_ids if past is defined in kwargs - if past: - input_ids = input_ids[:, -1].unsqueeze(-1) - if token_type_ids is not None: - token_type_ids = token_type_ids[:, -1].unsqueeze(-1) - - attention_mask = kwargs.get("attention_mask", None) - position_ids = kwargs.get("position_ids", None) - - if attention_mask is not None and position_ids is None: - # create position_ids on the fly for batch generation - position_ids = attention_mask.long().cumsum(-1) - 1 - position_ids.masked_fill_(attention_mask == 0, 1) - if past: - position_ids = position_ids[:, -1].unsqueeze(-1) - else: - position_ids = None - return { - "input_ids": input_ids, - "past_key_values": past, - "use_cache": kwargs.get("use_cache"), - "position_ids": position_ids, - "attention_mask": attention_mask, - "token_type_ids": token_type_ids, - } - - -def update_model_kwargs_fn(outputs: dict, **model_kwargs) -> dict: - if "past_key_values" in outputs: - model_kwargs["past"] = outputs["past_key_values"] - else: - model_kwargs["past"] = None - - # update token_type_ids with last value - if "token_type_ids" in model_kwargs: - token_type_ids = model_kwargs["token_type_ids"] - model_kwargs["token_type_ids"] = torch.cat( - [token_type_ids, token_type_ids[:, -1].unsqueeze(-1)], dim=-1 - ) - - # update attention mask - if "attention_mask" in model_kwargs: - attention_mask = model_kwargs["attention_mask"] - model_kwargs["attention_mask"] = torch.cat( - [attention_mask, attention_mask.new_ones((attention_mask.shape[0], 1))], - dim=-1, - ) - - return model_kwargs - - -def opt_prepare_inputs_fn( - input_ids: torch.Tensor, - past: Optional[torch.Tensor] = None, - attention_mask: Optional[torch.Tensor] = None, - use_cache: Optional[bool] = None, - **kwargs -) -> dict: - # if model is used as a decoder in encoder-decoder model, the decoder attention mask is created on the fly - if attention_mask is None: - attention_mask = input_ids.new_ones(input_ids.shape) - - if past: - input_ids = input_ids[:, -1:] - # first step, decoder_cached_states are empty - return { - "input_ids": input_ids, # encoder_outputs is defined. input_ids not needed - "attention_mask": attention_mask, - "past_key_values": past, - "use_cache": use_cache, - } - - -def bloom_prepare_inputs_fn( - input_ids: torch.Tensor, - past: Optional[torch.Tensor] = None, - attention_mask: Optional[torch.Tensor] = None, - use_cache: Optional[bool] = None, - **kwargs -) -> dict: - # if model is used as a decoder in encoder-decoder model, the decoder attention mask is created on the fly - if attention_mask is None: - attention_mask = input_ids.new_ones(input_ids.shape) - - if past: - input_ids = input_ids[:, -1:] - # first step, decoder_cached_states are empty - return { - "input_ids": input_ids, # encoder_outputs is defined. input_ids not needed - "attention_mask": attention_mask, - "past_key_values": past, - "use_cache": use_cache, - } diff --git a/neurons/text/prompting/miners/self_hosted/coati/models/gpt/__init__.py b/neurons/text/prompting/miners/self_hosted/coati/models/gpt/__init__.py deleted file mode 100644 index ad78b99cd2..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/models/gpt/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from .gpt_actor import GPTActor -from .gpt_critic import GPTCritic -from .gpt_lm import GPTLM -from .gpt_rm import GPTRM - -__all__ = ["GPTActor", "GPTCritic", "GPTRM", "GPTLM"] diff --git a/neurons/text/prompting/miners/self_hosted/coati/models/gpt/gpt_actor.py b/neurons/text/prompting/miners/self_hosted/coati/models/gpt/gpt_actor.py deleted file mode 100644 index 9ce698812b..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/models/gpt/gpt_actor.py +++ /dev/null @@ -1,37 +0,0 @@ -from typing import Optional - -from transformers.models.gpt2.configuration_gpt2 import GPT2Config -from transformers.models.gpt2.modeling_gpt2 import GPT2LMHeadModel - -from ..base import Actor - - -class GPTActor(Actor): - """ - GPT Actor model. - - Args: - pretrained (str): Pretrained model name or path. - config (GPT2Config): Model config. - checkpoint (bool): Enable gradient checkpointing. - lora_rank (int): Rank of the LoRa layer. - lora_train_bias (str): Bias training strategy for the LoRa layer. - """ - - def __init__( - self, - pretrained: Optional[str] = None, - config: Optional[GPT2Config] = None, - checkpoint: bool = False, - lora_rank: int = 0, - lora_train_bias: str = "none", - ) -> None: - if pretrained is not None: - model = GPT2LMHeadModel.from_pretrained(pretrained) - elif config is not None: - model = GPT2LMHeadModel(config) - else: - model = GPT2LMHeadModel(GPT2Config()) - if checkpoint: - model.gradient_checkpointing_enable() - super().__init__(model, lora_rank, lora_train_bias) diff --git a/neurons/text/prompting/miners/self_hosted/coati/models/gpt/gpt_critic.py b/neurons/text/prompting/miners/self_hosted/coati/models/gpt/gpt_critic.py deleted file mode 100644 index fc53d58482..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/models/gpt/gpt_critic.py +++ /dev/null @@ -1,39 +0,0 @@ -from typing import Optional - -import torch.nn as nn -from transformers.models.gpt2.configuration_gpt2 import GPT2Config -from transformers.models.gpt2.modeling_gpt2 import GPT2Model - -from ..base import Critic - - -class GPTCritic(Critic): - """ - GPT Critic model. - - Args: - pretrained (str): Pretrained model name or path. - config (GPT2Config): Model config. - checkpoint (bool): Enable gradient checkpointing. - lora_rank (int): Rank of the LO-RA decomposition. - lora_train_bias (str): LoRA bias training mode. - """ - - def __init__( - self, - pretrained: Optional[str] = None, - config: Optional[GPT2Config] = None, - checkpoint: bool = False, - lora_rank: int = 0, - lora_train_bias: str = "none", - ) -> None: - if pretrained is not None: - model = GPT2Model.from_pretrained(pretrained) - elif config is not None: - model = GPT2Model(config) - else: - model = GPT2Model(GPT2Config()) - if checkpoint: - model.gradient_checkpointing_enable() - value_head = nn.Linear(model.config.n_embd, 1) - super().__init__(model, value_head, lora_rank, lora_train_bias) diff --git a/neurons/text/prompting/miners/self_hosted/coati/models/gpt/gpt_lm.py b/neurons/text/prompting/miners/self_hosted/coati/models/gpt/gpt_lm.py deleted file mode 100644 index 0f478665ab..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/models/gpt/gpt_lm.py +++ /dev/null @@ -1,42 +0,0 @@ -from typing import Optional - -from transformers.models.gpt2.configuration_gpt2 import GPT2Config -from transformers.models.gpt2.modeling_gpt2 import GPT2LMHeadModel - -from ..base import LM - - -class GPTLM(LM): - """ - GPT language model. - - Args: - pretrained (str): Pretrained model name or path. - config (GPT2Config): Model config. - checkpoint (bool): Enable gradient checkpointing. - lora_rank (int): Rank of the LoRa layer. - lora_train_bias (str): Bias training strategy for the LoRa layer. - """ - - def __init__( - self, - pretrained: Optional[str] = None, - config: Optional[GPT2Config] = None, - checkpoint: bool = False, - lora_rank: int = 0, - lora_train_bias: str = "none", - ) -> None: - if pretrained is not None: - model = GPT2LMHeadModel.from_pretrained(pretrained) - elif config is not None: - model = GPT2LMHeadModel(config) - else: - model = GPT2LMHeadModel(GPT2Config()) - if checkpoint: - model.gradient_checkpointing_enable() - super().__init__(model, lora_rank, lora_train_bias) - - def forward(self, input_ids, attention_mask=None, labels=None, **kwargs): - return self.model( - input_ids, attention_mask=attention_mask, labels=labels, **kwargs - ) diff --git a/neurons/text/prompting/miners/self_hosted/coati/models/gpt/gpt_rm.py b/neurons/text/prompting/miners/self_hosted/coati/models/gpt/gpt_rm.py deleted file mode 100644 index 791f603e71..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/models/gpt/gpt_rm.py +++ /dev/null @@ -1,41 +0,0 @@ -from typing import Optional - -import torch.nn as nn -from transformers.models.gpt2.configuration_gpt2 import GPT2Config -from transformers.models.gpt2.modeling_gpt2 import GPT2Model - -from ..base import RewardModel - - -class GPTRM(RewardModel): - """ - GPT Reward model. - - Args: - pretrained (str): Pretrained model name or path. - config (GPT2Config): Model config. - checkpoint (bool): Enable gradient checkpointing. - lora_rank (int): Rank of the low-rank approximation. - lora_train_bias (str): LoRA bias training mode. - """ - - def __init__( - self, - pretrained: Optional[str] = None, - config: Optional[GPT2Config] = None, - checkpoint: bool = False, - lora_rank: int = 0, - lora_train_bias: str = "none", - ) -> None: - if pretrained is not None: - model = GPT2Model.from_pretrained(pretrained) - elif config is not None: - model = GPT2Model(config) - else: - model = GPT2Model(GPT2Config()) - if checkpoint: - model.gradient_checkpointing_enable() - - value_head = nn.Linear(model.config.n_embd, 1) - value_head.weight.data.normal_(mean=0.0, std=1 / (model.config.n_embd + 1)) - super().__init__(model, value_head, lora_rank, lora_train_bias) diff --git a/neurons/text/prompting/miners/self_hosted/coati/models/llama/__init__.py b/neurons/text/prompting/miners/self_hosted/coati/models/llama/__init__.py deleted file mode 100644 index 2a78bf3f7c..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/models/llama/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from .llama_actor import LlamaActor -from .llama_critic import LlamaCritic -from .llama_lm import LlamaLM -from .llama_rm import LlamaRM - -__all__ = ["LlamaActor", "LlamaCritic", "LlamaRM", "LlamaLM"] diff --git a/neurons/text/prompting/miners/self_hosted/coati/models/llama/llama_actor.py b/neurons/text/prompting/miners/self_hosted/coati/models/llama/llama_actor.py deleted file mode 100644 index 092f179f3e..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/models/llama/llama_actor.py +++ /dev/null @@ -1,39 +0,0 @@ -from typing import Optional - -import torch -from transformers import AutoModelForCausalLM, LlamaConfig, LlamaForCausalLM - -from ..base import Actor - - -class LlamaActor(Actor): - """ - Llama Actor model. - - Args: - pretrained (str): Pretrained model name or path. - config (LlamaConfig): Model config. - checkpoint (bool): Enable gradient checkpointing. - lora_rank (int): LoRA rank. - lora_train_bias (str): LoRA bias training mode. - """ - - def __init__( - self, - pretrained: Optional[str] = None, - config: Optional[LlamaConfig] = None, - checkpoint: bool = False, - lora_rank: int = 0, - lora_train_bias: str = "none", - ) -> None: - if pretrained is not None: - model = LlamaForCausalLM.from_pretrained(pretrained) - elif config is not None: - model = LlamaForCausalLM(config) - else: - model = LlamaForCausalLM(LlamaConfig()) - - if checkpoint: - model.gradient_checkpointing_enable() - - super().__init__(model, lora_rank, lora_train_bias) diff --git a/neurons/text/prompting/miners/self_hosted/coati/models/llama/llama_critic.py b/neurons/text/prompting/miners/self_hosted/coati/models/llama/llama_critic.py deleted file mode 100644 index 670bb39d88..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/models/llama/llama_critic.py +++ /dev/null @@ -1,43 +0,0 @@ -from typing import Optional - -import torch -import torch.nn as nn -from transformers import AutoModelForCausalLM, LlamaConfig, LlamaForCausalLM - -from ..base import Critic - - -class LlamaCritic(Critic): - """ - Llama Critic model. - - Args: - pretrained (str): Pretrained model name or path. - config (LlamaConfig): Model config. - checkpoint (bool): Enable gradient checkpointing. - lora_rank (int): LoRA rank. - lora_train_bias (str): LoRA bias training mode. - """ - - def __init__( - self, - pretrained: Optional[str] = None, - config: Optional[LlamaConfig] = None, - checkpoint: bool = False, - lora_rank: int = 0, - lora_train_bias: str = "none", - **kwargs - ) -> None: - if pretrained is not None: - model = LlamaForCausalLM.from_pretrained(pretrained) - elif config is not None: - model = LlamaForCausalLM(config) - else: - model = LlamaForCausalLM(LlamaConfig()) - - if checkpoint: - model.gradient_checkpointing_enable() - - value_head = nn.Linear(model.config.hidden_size, 1) - - super().__init__(model, value_head, lora_rank, lora_train_bias, **kwargs) diff --git a/neurons/text/prompting/miners/self_hosted/coati/models/llama/llama_lm.py b/neurons/text/prompting/miners/self_hosted/coati/models/llama/llama_lm.py deleted file mode 100644 index d951cf54fc..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/models/llama/llama_lm.py +++ /dev/null @@ -1,43 +0,0 @@ -from typing import Optional - -from transformers import LlamaConfig, LlamaForCausalLM - -from ..base import LM - - -class LlamaLM(LM): - """ - Llama language model. - - Args: - pretrained (str): Pretrained model name or path. - config (LlamaConfig): Model config. - checkpoint (bool): Enable gradient checkpointing. - lora_rank (int): LoRA rank. - lora_train_bias (str): LoRA bias training mode. - """ - - def __init__( - self, - pretrained: Optional[str] = None, - config: Optional[LlamaConfig] = None, - checkpoint: bool = False, - lora_rank: int = 0, - lora_train_bias: str = "none", - ) -> None: - if pretrained is not None: - model = LlamaForCausalLM.from_pretrained(pretrained) - elif config is not None: - model = LlamaForCausalLM(config) - else: - model = LlamaForCausalLM(LlamaConfig()) - - if checkpoint: - model.gradient_checkpointing_enable() - - super().__init__(model, lora_rank, lora_train_bias) - - def forward(self, input_ids, attention_mask=None, labels=None, **kwargs): - return self.model( - input_ids, attention_mask=attention_mask, labels=labels, **kwargs - ) diff --git a/neurons/text/prompting/miners/self_hosted/coati/models/llama/llama_rm.py b/neurons/text/prompting/miners/self_hosted/coati/models/llama/llama_rm.py deleted file mode 100644 index e0ca6d5163..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/models/llama/llama_rm.py +++ /dev/null @@ -1,41 +0,0 @@ -from typing import Optional - -import torch.nn as nn -from transformers import LlamaConfig, LlamaForCausalLM, LlamaModel - -from ..base import RewardModel - - -class LlamaRM(RewardModel): - """ - Llama Reward model. - - Args: - pretrained (str): Pretrained model name or path. - config (LlamaConfig): Model config. - checkpoint (bool): Enable gradient checkpointing. - lora_rank (int): LoRA rank. - lora_train_bias (str): LoRA bias training mode. - """ - - def __init__( - self, - pretrained: Optional[str] = None, - config: Optional[LlamaConfig] = None, - checkpoint: bool = False, - lora_rank: int = 0, - lora_train_bias: str = "none", - ) -> None: - if pretrained is not None: - model = LlamaModel.from_pretrained(pretrained) - elif config is not None: - model = LlamaModel(config) - else: - model = LlamaModel(LlamaConfig()) - - if checkpoint: - model.gradient_checkpointing_enable() - value_head = nn.Linear(model.config.hidden_size, 1) - value_head.weight.data.normal_(mean=0.0, std=1 / (model.config.hidden_size + 1)) - - super().__init__(model, value_head, lora_rank, lora_train_bias) diff --git a/neurons/text/prompting/miners/self_hosted/coati/models/lora.py b/neurons/text/prompting/miners/self_hosted/coati/models/lora.py deleted file mode 100644 index 486929c71b..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/models/lora.py +++ /dev/null @@ -1,135 +0,0 @@ -import math -from typing import Optional - -import loralib as lora -import torch -import torch.nn as nn -import torch.nn.functional as F - - -class LoraLinear(lora.LoRALayer, nn.Module): - """Replace in-place ops to out-of-place ops to fit gemini. Convert a torch.nn.Linear to LoraLinear.""" - - def __init__( - self, - weight: nn.Parameter, - bias: Optional[nn.Parameter], - r: int = 0, - lora_alpha: int = 1, - lora_dropout: float = 0.0, - fan_in_fan_out: bool = False, # Set this to True if the layer to replace stores weight like (fan_in, fan_out) - merge_weights: bool = True, - ): - nn.Module.__init__(self) - lora.LoRALayer.__init__( - self, - r=r, - lora_alpha=lora_alpha, - lora_dropout=lora_dropout, - merge_weights=merge_weights, - ) - self.weight = weight - self.bias = bias - - out_features, in_features = weight.shape - self.in_features = in_features - self.out_features = out_features - - self.fan_in_fan_out = fan_in_fan_out - # Actual trainable parameters - if r > 0: - self.lora_A = nn.Parameter(self.weight.new_zeros((r, in_features))) - self.lora_B = nn.Parameter(self.weight.new_zeros((out_features, r))) - self.scaling = self.lora_alpha / self.r - # Freezing the pre-trained weight matrix - self.weight.requires_grad = False - self.reset_parameters() - if fan_in_fan_out: - self.weight.data = self.weight.data.T - - def reset_parameters(self): - if hasattr(self, "lora_A"): - # initialize A the same way as the default for nn.Linear and B to zero - nn.init.kaiming_uniform_(self.lora_A, a=math.sqrt(5)) - nn.init.zeros_(self.lora_B) - - def train(self, mode: bool = True): - def T(w): - return w.T if self.fan_in_fan_out else w - - nn.Module.train(self, mode) - if self.merge_weights and self.merged: - # Make sure that the weights are not merged - if self.r > 0: - self.weight.data -= T(self.lora_B @ self.lora_A) * self.scaling - self.merged = False - - def eval(self): - def T(w): - return w.T if self.fan_in_fan_out else w - - nn.Module.eval(self) - if self.merge_weights and not self.merged: - # Merge the weights and mark it - if self.r > 0: - self.weight.data += T(self.lora_B @ self.lora_A) * self.scaling - delattr(self, "lora_A") - delattr(self, "lora_B") - self.merged = True - - def forward(self, x: torch.Tensor): - def T(w): - return w.T if self.fan_in_fan_out else w - - if self.r > 0 and not self.merged: - result = F.linear(x, T(self.weight), bias=self.bias) - if self.r > 0: - result = ( - result - + (self.lora_dropout(x) @ self.lora_A.t() @ self.lora_B.t()) - * self.scaling - ) - return result - else: - return F.linear(x, T(self.weight), bias=self.bias) - - -def lora_linear_wrapper(linear: nn.Linear, lora_rank: int) -> LoraLinear: - assert ( - lora_rank <= linear.in_features - ), f"LoRA rank ({lora_rank}) must be less than or equal to in features ({linear.in_features})" - lora_linear = LoraLinear( - linear.weight, linear.bias, r=lora_rank, merge_weights=False - ) - return lora_linear - - -def convert_to_lora_recursively(module: nn.Module, lora_rank: int) -> None: - for name, child in module.named_children(): - if isinstance(child, nn.Linear): - setattr(module, name, lora_linear_wrapper(child, lora_rank)) - else: - convert_to_lora_recursively(child, lora_rank) - - -class LoRAModule(nn.Module): - """A LoRA module base class. All derived classes should call `convert_to_lora()` at the bottom of `__init__()`. - This calss will convert all torch.nn.Linear layer to LoraLinear layer. - - Args: - lora_rank (int, optional): LoRA rank. 0 means LoRA is not applied. Defaults to 0. - lora_train_bias (str, optional): Whether LoRA train biases. - 'none' means it doesn't train biases. 'all' means it trains all biases. 'lora_only' means it only trains biases of LoRA layers. - Defaults to 'none'. - """ - - def __init__(self, lora_rank: int = 0, lora_train_bias: str = "none") -> None: - super().__init__() - self.lora_rank = lora_rank - self.lora_train_bias = lora_train_bias - - def convert_to_lora(self) -> None: - if self.lora_rank <= 0: - return - convert_to_lora_recursively(self, self.lora_rank) - lora.mark_only_lora_as_trainable(self, self.lora_train_bias) diff --git a/neurons/text/prompting/miners/self_hosted/coati/models/loss.py b/neurons/text/prompting/miners/self_hosted/coati/models/loss.py deleted file mode 100644 index fefe2e7f19..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/models/loss.py +++ /dev/null @@ -1,138 +0,0 @@ -from typing import Optional - -import torch -import torch.nn as nn - -from .utils import masked_mean - - -class GPTLMLoss(nn.Module): - """ - GPT Language Model Loss - """ - - def __init__(self): - super().__init__() - self.loss = nn.CrossEntropyLoss() - - def forward(self, logits: torch.Tensor, labels: torch.Tensor) -> torch.Tensor: - shift_logits = logits[..., :-1, :].contiguous() - shift_labels = labels[..., 1:].contiguous() - # Flatten the tokens - return self.loss( - shift_logits.view(-1, shift_logits.size(-1)), shift_labels.view(-1) - ) - - -class PolicyLoss(nn.Module): - """ - Policy Loss for PPO - """ - - def __init__(self, clip_eps: float = 0.2) -> None: - super().__init__() - self.clip_eps = clip_eps - - def forward( - self, - log_probs: torch.Tensor, - old_log_probs: torch.Tensor, - advantages: torch.Tensor, - action_mask: Optional[torch.Tensor] = None, - ) -> torch.Tensor: - ratio = (log_probs - old_log_probs).exp() - surr1 = ratio * advantages - surr2 = ratio.clamp(1 - self.clip_eps, 1 + self.clip_eps) * advantages - loss = -torch.min(surr1, surr2) - if action_mask is not None: - loss = masked_mean(loss, action_mask) - loss = loss.mean() - return loss - - -class ValueLoss(nn.Module): - """ - Value Loss for PPO - """ - - def __init__(self, clip_eps: float = 0.4) -> None: - super().__init__() - self.clip_eps = clip_eps - - def forward( - self, - values: torch.Tensor, - old_values: torch.Tensor, - reward: torch.Tensor, - action_mask: Optional[torch.Tensor] = None, - ) -> torch.Tensor: - values_clipped = old_values + (values - old_values).clamp( - -self.clip_eps, self.clip_eps - ) - surr1 = (values_clipped - reward) ** 2 - surr2 = (values - reward) ** 2 - loss = torch.max(surr1, surr2) - loss = loss.mean() - return loss - - -class PPOPtxActorLoss(nn.Module): - """ - To Do: - - PPO-ptx Actor Loss - """ - - def __init__( - self, - policy_clip_eps: float = 0.2, - pretrain_coef: float = 0.0, - pretrain_loss_fn=GPTLMLoss(), - ) -> None: - super().__init__() - self.pretrain_coef = pretrain_coef - self.policy_loss_fn = PolicyLoss(clip_eps=policy_clip_eps) - self.pretrain_loss_fn = pretrain_loss_fn - - def forward( - self, - log_probs: torch.Tensor, - old_log_probs: torch.Tensor, - advantages: torch.Tensor, - lm_logits: torch.Tensor, - lm_input_ids: torch.Tensor, - action_mask: Optional[torch.Tensor] = None, - ) -> torch.Tensor: - policy_loss = self.policy_loss_fn( - log_probs, old_log_probs, advantages, action_mask=action_mask - ) - lm_loss = self.pretrain_loss_fn(lm_logits, lm_input_ids) - return policy_loss + self.pretrain_coef * lm_loss - - -class LogSigLoss(nn.Module): - """ - Pairwise Loss for Reward Model - Details: https://arxiv.org/abs/2203.02155 - """ - - def forward( - self, chosen_reward: torch.Tensor, reject_reward: torch.Tensor - ) -> torch.Tensor: - probs = torch.sigmoid(chosen_reward - reject_reward) - log_probs = torch.log(probs) - loss = -log_probs.mean() - return loss - - -class LogExpLoss(nn.Module): - """ - Pairwise Loss for Reward Model - Details: https://arxiv.org/abs/2204.05862 - """ - - def forward( - self, chosen_reward: torch.Tensor, reject_reward: torch.Tensor - ) -> torch.Tensor: - loss = torch.log(1 + torch.exp(reject_reward - chosen_reward)).mean() - return loss diff --git a/neurons/text/prompting/miners/self_hosted/coati/models/opt/__init__.py b/neurons/text/prompting/miners/self_hosted/coati/models/opt/__init__.py deleted file mode 100644 index ede77a49aa..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/models/opt/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from .opt_actor import OPTActor -from .opt_critic import OPTCritic -from .opt_lm import OPTLM -from .opt_rm import OPTRM - -__all__ = ["OPTActor", "OPTCritic", "OPTRM", "OPTLM"] diff --git a/neurons/text/prompting/miners/self_hosted/coati/models/opt/opt_actor.py b/neurons/text/prompting/miners/self_hosted/coati/models/opt/opt_actor.py deleted file mode 100644 index cd8908e13f..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/models/opt/opt_actor.py +++ /dev/null @@ -1,37 +0,0 @@ -from typing import Optional - -from transformers.models.opt.configuration_opt import OPTConfig -from transformers.models.opt.modeling_opt import OPTForCausalLM - -from ..base import Actor - - -class OPTActor(Actor): - """ - OPT Actor model. - - Args: - pretrained (str): Pretrained model name or path. - config (OPTConfig): Model config. - checkpoint (bool): Enable gradient checkpointing. - lora_rank (int): Rank of the low-rank approximation. - lora_train_bias (str): LoRA bias training mode. - """ - - def __init__( - self, - pretrained: Optional[str] = None, - config: Optional[OPTConfig] = None, - checkpoint: bool = False, - lora_rank: int = 0, - lora_train_bias: str = "none", - ) -> None: - if pretrained is not None: - model = OPTForCausalLM.from_pretrained(pretrained) - elif config is not None: - model = OPTForCausalLM(config) - else: - model = OPTForCausalLM(OPTConfig()) - if checkpoint: - model.gradient_checkpointing_enable() - super().__init__(model, lora_rank, lora_train_bias) diff --git a/neurons/text/prompting/miners/self_hosted/coati/models/opt/opt_critic.py b/neurons/text/prompting/miners/self_hosted/coati/models/opt/opt_critic.py deleted file mode 100644 index 5a8577e026..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/models/opt/opt_critic.py +++ /dev/null @@ -1,40 +0,0 @@ -from typing import Optional - -import torch.nn as nn -from transformers.models.opt.configuration_opt import OPTConfig -from transformers.models.opt.modeling_opt import OPTModel - -from ..base import Critic - - -class OPTCritic(Critic): - """ - OPT Critic model. - - Args: - pretrained (str): Pretrained model name or path. - config (OPTConfig): Model config. - checkpoint (bool): Enable gradient checkpointing. - lora_rank (int): Rank of the low-rank approximation. - lora_train_bias (str): LoRA bias training mode. - """ - - def __init__( - self, - pretrained: Optional[str] = None, - config: Optional[OPTConfig] = None, - checkpoint: bool = False, - lora_rank: int = 0, - lora_train_bias: str = "none", - **kwargs - ) -> None: - if pretrained is not None: - model = OPTModel.from_pretrained(pretrained) - elif config is not None: - model = OPTModel(config) - else: - model = OPTModel(OPTConfig()) - if checkpoint: - model.gradient_checkpointing_enable() - value_head = nn.Linear(model.config.word_embed_proj_dim, 1) - super().__init__(model, value_head, lora_rank, lora_train_bias, **kwargs) diff --git a/neurons/text/prompting/miners/self_hosted/coati/models/opt/opt_lm.py b/neurons/text/prompting/miners/self_hosted/coati/models/opt/opt_lm.py deleted file mode 100644 index 0675fa48a3..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/models/opt/opt_lm.py +++ /dev/null @@ -1,42 +0,0 @@ -from typing import Optional - -from transformers.models.opt.configuration_opt import OPTConfig -from transformers.models.opt.modeling_opt import OPTForCausalLM - -from ..base import LM - - -class OPTLM(LM): - """ - OPT language model. - - Args: - pretrained (str): Pretrained model name or path. - config (OPTConfig): Model config. - checkpoint (bool): Enable gradient checkpointing. - lora_rank (int): Rank of the low-rank approximation. - lora_train_bias (str): LoRA bias training mode. - """ - - def __init__( - self, - pretrained: Optional[str] = None, - config: Optional[OPTConfig] = None, - checkpoint: bool = False, - lora_rank: int = 0, - lora_train_bias: str = "none", - ) -> None: - if pretrained is not None: - model = OPTForCausalLM.from_pretrained(pretrained) - elif config is not None: - model = OPTForCausalLM(config) - else: - model = OPTForCausalLM(OPTConfig()) - if checkpoint: - model.gradient_checkpointing_enable() - super().__init__(model, lora_rank, lora_train_bias) - - def forward(self, input_ids, attention_mask=None, labels=None, **kwargs): - return self.model( - input_ids, attention_mask=attention_mask, labels=labels, **kwargs - ) diff --git a/neurons/text/prompting/miners/self_hosted/coati/models/opt/opt_rm.py b/neurons/text/prompting/miners/self_hosted/coati/models/opt/opt_rm.py deleted file mode 100644 index 66e5e35cd0..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/models/opt/opt_rm.py +++ /dev/null @@ -1,42 +0,0 @@ -from typing import Optional - -import torch.nn as nn -from transformers import OPTConfig, OPTModel - -from ..base import RewardModel - - -class OPTRM(RewardModel): - """ - OPT Reward model. - - Args: - pretrained (str): Pretrained model name or path. - config (OPTConfig): Model config. - checkpoint (bool): Enable gradient checkpointing. - lora_rank (int): Rank of the low-rank approximation. - lora_train_bias (str): LoRA bias training mode. - """ - - def __init__( - self, - pretrained: Optional[str] = None, - config: Optional[OPTConfig] = None, - checkpoint: bool = False, - lora_rank: int = 0, - lora_train_bias: str = "none", - ) -> None: - if pretrained is not None: - model = OPTModel.from_pretrained(pretrained) - elif config is not None: - model = OPTModel(config) - else: - model = OPTModel(OPTConfig()) - if checkpoint: - model.gradient_checkpointing_enable() - - value_head = nn.Linear(model.config.word_embed_proj_dim, 1) - value_head.weight.data.normal_( - mean=0.0, std=1 / (model.config.word_embed_proj_dim + 1) - ) - super().__init__(model, value_head, lora_rank, lora_train_bias) diff --git a/neurons/text/prompting/miners/self_hosted/coati/models/roberta/__init__.py b/neurons/text/prompting/miners/self_hosted/coati/models/roberta/__init__.py deleted file mode 100644 index cf328b22c7..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/models/roberta/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from .roberta_actor import RoBERTaActor -from .roberta_critic import RoBERTaCritic -from .roberta_rm import RoBERTaRM - -__all__ = ["RoBERTaActor", "RoBERTaCritic", "RoBERTaRM"] diff --git a/neurons/text/prompting/miners/self_hosted/coati/models/roberta/roberta_actor.py b/neurons/text/prompting/miners/self_hosted/coati/models/roberta/roberta_actor.py deleted file mode 100644 index 1500ded69b..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/models/roberta/roberta_actor.py +++ /dev/null @@ -1,37 +0,0 @@ -from typing import Optional - -from transformers.models.roberta.configuration_roberta import RobertaConfig -from transformers.models.roberta.modeling_roberta import RobertaForCausalLM - -from ..base import Actor - - -class RoBERTaActor(Actor): - """ - RoBERTa Actor model. - - Args: - pretrained (str): Pretrained model name or path. - config (RoBERTaConfig): Model config. - checkpoint (bool): Enable gradient checkpointing. - lora_rank (int): Rank of the low-rank approximation. - lora_train_bias (str): LoRA bias training mode. - """ - - def __init__( - self, - pretrained: Optional[str] = None, - config: Optional[RobertaConfig] = None, - checkpoint: bool = False, - lora_rank: int = 0, - lora_train_bias: str = "none", - ) -> None: - if pretrained is not None: - model = RobertaForCausalLM.from_pretrained(pretrained) - elif config is not None: - model = RobertaForCausalLM(config) - else: - model = RobertaForCausalLM(RobertaConfig()) - if checkpoint: - model.gradient_checkpointing_enable() - super().__init__(model, lora_rank, lora_train_bias) diff --git a/neurons/text/prompting/miners/self_hosted/coati/models/roberta/roberta_critic.py b/neurons/text/prompting/miners/self_hosted/coati/models/roberta/roberta_critic.py deleted file mode 100644 index 80c4a708ae..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/models/roberta/roberta_critic.py +++ /dev/null @@ -1,40 +0,0 @@ -from typing import Optional - -import torch.nn as nn -from transformers.models.roberta.configuration_roberta import RobertaConfig -from transformers.models.roberta.modeling_roberta import RobertaModel - -from ..base import Critic - - -class RoBERTaCritic(Critic): - """ - RoBERTa Critic model. - - Args: - pretrained (str): Pretrained model name or path. - config (RoBERTa Config): Model config. - checkpoint (bool): Enable gradient checkpointing. - lora_rank (int): Rank of the low-rank approximation. - lora_train_bias (str): LoRA bias training mode. - """ - - def __init__( - self, - pretrained: Optional[str] = None, - config: Optional[RobertaConfig] = None, - checkpoint: bool = False, - lora_rank: int = 0, - lora_train_bias: str = "none", - **kwargs - ) -> None: - if pretrained is not None: - model = RobertaModel.from_pretrained(pretrained, add_pooling_layer=False) - elif config is not None: - model = RobertaModel(config) - else: - model = RobertaModel(RobertaConfig()) - if checkpoint: - model.gradient_checkpointing_enable() - value_head = nn.Linear(model.config.hidden_size, 1) - super().__init__(model, value_head, lora_rank, lora_train_bias, **kwargs) diff --git a/neurons/text/prompting/miners/self_hosted/coati/models/roberta/roberta_rm.py b/neurons/text/prompting/miners/self_hosted/coati/models/roberta/roberta_rm.py deleted file mode 100644 index 4d6a4c5898..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/models/roberta/roberta_rm.py +++ /dev/null @@ -1,41 +0,0 @@ -from typing import Optional - -import torch.nn as nn -from transformers import RobertaConfig, RobertaModel - - -from ..base import RewardModel - - -class RoBERTaRM(RewardModel): - """ - RoBERTa Reward model. - - Args: - pretrained (str): Pretrained model name or path. - config (RoBERTaConfig): Model config. - checkpoint (bool): Enable gradient checkpointing. - lora_rank (int): Rank of the low-rank approximation. - lora_train_bias (str): LoRA bias training mode. - """ - - def __init__( - self, - pretrained: Optional[str] = None, - config: Optional[RobertaConfig] = None, - checkpoint: bool = False, - lora_rank: int = 0, - lora_train_bias: str = "none", - ) -> None: - if pretrained is not None: - model = RobertaModel.from_pretrained(pretrained, add_pooling_layer=False) - elif config is not None: - model = RobertaModel(config) - else: - model = RobertaModel(RobertaConfig()) - if checkpoint: - model.gradient_checkpointing_enable() - - value_head = nn.Linear(model.config.hidden_size, 1) - value_head.weight.data.normal_(mean=0.0, std=1 / (model.config.hidden_size + 1)) - super().__init__(model, value_head, lora_rank, lora_train_bias) diff --git a/neurons/text/prompting/miners/self_hosted/coati/models/utils.py b/neurons/text/prompting/miners/self_hosted/coati/models/utils.py deleted file mode 100644 index e930617613..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/models/utils.py +++ /dev/null @@ -1,104 +0,0 @@ -from typing import Optional, Union - -import loralib as lora -import torch -import torch.nn as nn -import torch.nn.functional as F - - -def compute_approx_kl( - log_probs: torch.Tensor, - log_probs_base: torch.Tensor, - action_mask: Optional[torch.Tensor] = None, -) -> torch.Tensor: - """ - Compute the approximate KL divergence between two distributions. - Schulman blog: http://joschu.net/blog/kl-approx.html - - Args: - log_probs: Log probabilities of the new distribution. - log_probs_base: Log probabilities of the base distribution. - action_mask: Mask for actions. - """ - - log_ratio = log_probs - log_probs_base - approx_kl = (log_ratio.exp() - 1) - log_ratio - if action_mask is not None: - approx_kl = masked_mean(approx_kl, action_mask, dim=1) - return approx_kl - approx_kl = approx_kl.mean(dim=1) - return approx_kl - - -def compute_reward( - r: Union[torch.Tensor, float], - kl_coef: float, - log_probs: torch.Tensor, - log_probs_base: torch.Tensor, - action_mask: Optional[torch.Tensor] = None, -) -> torch.Tensor: - if kl_coef <= 0.0: - return r - kl = compute_approx_kl(log_probs, log_probs_base, action_mask=action_mask) - reward = r - kl_coef * kl - return reward - - -def log_probs_from_logits(logits: torch.Tensor, labels: torch.Tensor) -> torch.Tensor: - log_probs = F.log_softmax(logits, dim=-1) - log_probs_labels = log_probs.gather(dim=-1, index=labels.unsqueeze(-1)) - return log_probs_labels.squeeze(-1) - - -def masked_mean(tensor: torch.Tensor, mask: torch.Tensor, dim: int = 1) -> torch.Tensor: - tensor = tensor * mask - tensor = tensor.sum(dim=dim) - mask_sum = mask.sum(dim=dim) - mean = tensor / (mask_sum + 1e-8) - return mean - - -def masked_normalize( - tensor: torch.Tensor, mask: torch.Tensor, dim: int = 1, eps: float = 1e-8 -) -> torch.Tensor: - tensor = tensor * mask - mean = masked_mean(tensor, mask, dim=dim) - mean_centered = tensor - mean - var = masked_mean(mean_centered**2, mask, dim=dim) - return mean_centered * var.clamp(min=eps).rsqrt() - - -def normalize(tensor: torch.Tensor, dim: int = 0, eps: float = 1e-8) -> torch.Tensor: - mean = tensor.mean(dim) - mean_centered = tensor - mean - var = (mean_centered**2).mean(dim) - norm = mean_centered * var.clamp(min=eps).rsqrt() - return norm - - -def convert_to_lora( - model: nn.Module, - input_size: int, - output_size: int, - lora_rank: int = 16, - lora_alpha: int = 1, - lora_dropout: float = 0.0, - fan_in_fan_out: bool = False, - merge_weights: bool = True, -): - if lora_rank > min(input_size, output_size): - raise ValueError( - f"LoRA rank {lora_rank} must be less or equal than {min(input_size, output_size)}" - ) - - for name, module in model.named_modules(): - if isinstance(module, nn.Linear): - module._modules[name] = lora.Linear( - input_size, - output_size, - r=lora_rank, - lora_alpha=lora_alpha, - lora_dropout=lora_dropout, - fan_in_fan_out=fan_in_fan_out, - merge_weights=merge_weights, - ) diff --git a/neurons/text/prompting/miners/self_hosted/coati/replay_buffer/__init__.py b/neurons/text/prompting/miners/self_hosted/coati/replay_buffer/__init__.py deleted file mode 100644 index 5b34317a9b..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/replay_buffer/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from .base import ReplayBuffer -from .naive import NaiveReplayBuffer - -__all__ = ["ReplayBuffer", "NaiveReplayBuffer"] diff --git a/neurons/text/prompting/miners/self_hosted/coati/replay_buffer/base.py b/neurons/text/prompting/miners/self_hosted/coati/replay_buffer/base.py deleted file mode 100644 index 9290fd90f3..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/replay_buffer/base.py +++ /dev/null @@ -1,43 +0,0 @@ -from abc import ABC, abstractmethod -from typing import Any - -from coati.experience_maker.base import Experience - - -class ReplayBuffer(ABC): - """Replay buffer base class. It stores experience. - - Args: - sample_batch_size (int): Batch size when sampling. - limit (int, optional): Limit of number of experience samples. A number <= 0 means unlimited. Defaults to 0. - """ - - def __init__(self, sample_batch_size: int, limit: int = 0) -> None: - super().__init__() - self.sample_batch_size = sample_batch_size - # limit <= 0 means unlimited - self.limit = limit - - @abstractmethod - def append(self, experience: Experience) -> None: - pass - - @abstractmethod - def clear(self) -> None: - pass - - @abstractmethod - def sample(self) -> Experience: - pass - - @abstractmethod - def __len__(self) -> int: - pass - - @abstractmethod - def __getitem__(self, idx: int) -> Any: - pass - - @abstractmethod - def collate_fn(self, batch: Any) -> Experience: - pass diff --git a/neurons/text/prompting/miners/self_hosted/coati/replay_buffer/naive.py b/neurons/text/prompting/miners/self_hosted/coati/replay_buffer/naive.py deleted file mode 100644 index 53326cf077..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/replay_buffer/naive.py +++ /dev/null @@ -1,59 +0,0 @@ -import random -from typing import List - -import torch -from coati.experience_maker.base import Experience - -from .base import ReplayBuffer -from .utils import BufferItem, make_experience_batch, split_experience_batch - - -class NaiveReplayBuffer(ReplayBuffer): - """Naive replay buffer class. It stores experience. - - Args: - sample_batch_size (int): Batch size when sampling. - limit (int, optional): Limit of number of experience samples. A number <= 0 means unlimited. Defaults to 0. - cpu_offload (bool, optional): Whether to offload experience to cpu when sampling. Defaults to True. - """ - - def __init__( - self, sample_batch_size: int, limit: int = 0, cpu_offload: bool = True - ) -> None: - super().__init__(sample_batch_size, limit) - self.cpu_offload = cpu_offload - self.target_device = torch.device(f"cuda:{torch.cuda.current_device()}") - # TODO(ver217): add prefetch - self.items: List[BufferItem] = [] - - @torch.no_grad() - def append(self, experience: Experience) -> None: - if self.cpu_offload: - experience.to_device(torch.device("cpu")) - items = split_experience_batch(experience) - self.items.extend(items) - if self.limit > 0: - samples_to_remove = len(self.items) - self.limit - if samples_to_remove > 0: - self.items = self.items[samples_to_remove:] - - def clear(self) -> None: - self.items.clear() - - @torch.no_grad() - def sample(self) -> Experience: - items = random.sample(self.items, self.sample_batch_size) - experience = make_experience_batch(items) - if self.cpu_offload: - experience.to_device(self.target_device) - return experience - - def __len__(self) -> int: - return len(self.items) - - def __getitem__(self, idx: int) -> BufferItem: - return self.items[idx] - - def collate_fn(self, batch) -> Experience: - experience = make_experience_batch(batch) - return experience diff --git a/neurons/text/prompting/miners/self_hosted/coati/replay_buffer/utils.py b/neurons/text/prompting/miners/self_hosted/coati/replay_buffer/utils.py deleted file mode 100644 index 5562ce8a7d..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/replay_buffer/utils.py +++ /dev/null @@ -1,92 +0,0 @@ -from dataclasses import dataclass -from typing import List, Optional - -import torch -import torch.nn.functional as F -from coati.experience_maker.base import Experience - - -@dataclass -class BufferItem: - """BufferItem is an item of experience data. - - Shapes of each tensor: - sequences: (S) - action_log_probs: (A) - values: (1) - reward: (1) - advatanges: (1) - attention_mask: (S) - action_mask: (A) - - "A" is the number of actions. - """ - - sequences: torch.Tensor - action_log_probs: torch.Tensor - values: torch.Tensor - reward: torch.Tensor - advantages: torch.Tensor - attention_mask: Optional[torch.LongTensor] - action_mask: Optional[torch.BoolTensor] - - -def split_experience_batch(experience: Experience) -> List[BufferItem]: - batch_size = experience.sequences.size(0) - batch_kwargs = [{} for _ in range(batch_size)] - keys = ( - "sequences", - "action_log_probs", - "values", - "reward", - "advantages", - "attention_mask", - "action_mask", - ) - for key in keys: - value = getattr(experience, key) - if isinstance(value, torch.Tensor): - vals = torch.unbind(value) - else: - # None - vals = [value for _ in range(batch_size)] - assert batch_size == len(vals) - for i, v in enumerate(vals): - batch_kwargs[i][key] = v - items = [BufferItem(**kwargs) for kwargs in batch_kwargs] - return items - - -def zero_pad_sequences( - sequences: List[torch.Tensor], side: str = "left" -) -> torch.Tensor: - assert side in ("left", "right") - max_len = max(seq.size(0) for seq in sequences) - padded_sequences = [] - for seq in sequences: - pad_len = max_len - seq.size(0) - padding = (pad_len, 0) if side == "left" else (0, pad_len) - padded_sequences.append(F.pad(seq, padding)) - return torch.stack(padded_sequences, dim=0) - - -def make_experience_batch(items: List[BufferItem]) -> Experience: - kwargs = {} - to_pad_keys = set(("action_log_probs", "action_mask")) - keys = ( - "sequences", - "action_log_probs", - "values", - "reward", - "advantages", - "attention_mask", - "action_mask", - ) - for key in keys: - vals = [getattr(item, key) for item in items] - if key in to_pad_keys: - batch_data = zero_pad_sequences(vals) - else: - batch_data = torch.stack(vals, dim=0) - kwargs[key] = batch_data - return Experience(**kwargs) diff --git a/neurons/text/prompting/miners/self_hosted/coati/trainer/__init__.py b/neurons/text/prompting/miners/self_hosted/coati/trainer/__init__.py deleted file mode 100644 index da00e0179a..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/trainer/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from .base import Trainer -from .ppo import PPOTrainer -from .rm import RewardModelTrainer -from .sft import SFTTrainer - -__all__ = ["Trainer", "PPOTrainer", "RewardModelTrainer", "SFTTrainer"] diff --git a/neurons/text/prompting/miners/self_hosted/coati/trainer/base.py b/neurons/text/prompting/miners/self_hosted/coati/trainer/base.py deleted file mode 100644 index a7685371de..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/trainer/base.py +++ /dev/null @@ -1,186 +0,0 @@ -from abc import ABC, abstractmethod -from typing import Any, Callable, Dict, List, Optional, Union - -import torch -from coati.experience_maker import Experience, ExperienceMaker -from coati.replay_buffer import ReplayBuffer -from torch import Tensor -from torch.utils.data import DistributedSampler -from tqdm import tqdm - -from .callbacks import Callback -from .strategies import Strategy -from .utils import is_rank_0 - - -class Trainer(ABC): - """ - Base class for rlhf trainers. - - Args: - strategy (Strategy):the strategy to use for training - experience_maker (ExperienceMaker): the experience maker to use for produce experience to fullfill replay buffer - replay_buffer (ReplayBuffer): the replay buffer to use for training - experience_batch_size (int, defaults to 8): the batch size to use for experience generation - max_epochs (int, defaults to 1): the number of epochs of training process - tokenizer (Callable, optional): the tokenizer to use for tokenizing the input - sample_replay_buffer (bool, defaults to False): whether to sample from replay buffer - data_loader_pin_memory (bool, defaults to True): whether to pin memory for data loader - callbacks (List[Callback], defaults to []): the callbacks to call during training process - generate_kwargs (dict, optional): the kwargs to use while model generating - """ - - def __init__( - self, - strategy: Strategy, - experience_maker: ExperienceMaker, - replay_buffer: ReplayBuffer, - experience_batch_size: int = 8, - max_epochs: int = 1, - tokenizer: Optional[Callable[[Any], dict]] = None, - sample_replay_buffer: bool = False, - dataloader_pin_memory: bool = True, - callbacks: List[Callback] = [], - **generate_kwargs, - ) -> None: - super().__init__() - self.strategy = strategy - self.experience_maker = experience_maker - self.replay_buffer = replay_buffer - self.experience_batch_size = experience_batch_size - self.max_epochs = max_epochs - self.tokenizer = tokenizer - self.generate_kwargs = generate_kwargs - self.sample_replay_buffer = sample_replay_buffer - self.dataloader_pin_memory = dataloader_pin_memory - self.callbacks = callbacks - - @abstractmethod - def training_step(self, experience: Experience) -> Dict[str, Any]: - pass - - def _make_experience(self, inputs: Union[Tensor, Dict[str, Tensor]]) -> Experience: - if isinstance(inputs, Tensor): - return self.experience_maker.make_experience(inputs, **self.generate_kwargs) - elif isinstance(inputs, dict): - return self.experience_maker.make_experience( - **inputs, **self.generate_kwargs - ) - else: - raise ValueError(f'Unsupported input type "{type(inputs)}"') - - def _sample_prompts(self, prompts) -> list: - indices = list(range(len(prompts))) - sampled_indices = self.strategy.experience_sampler.choice( - indices, self.experience_batch_size, replace=False - ) - return [prompts[i] for i in sampled_indices] - - def _learn(self): - # replay buffer may be empty at first, we should rebuild at each training - if not self.sample_replay_buffer: - dataloader = self.strategy.setup_dataloader( - self.replay_buffer, self.dataloader_pin_memory - ) - device = torch.cuda.current_device() - if self.sample_replay_buffer: - pbar = tqdm( - range(self.max_epochs), desc="Train epoch", disable=not is_rank_0() - ) - for _ in pbar: - experience = self.replay_buffer.sample() - metrics = self.training_step(experience) - pbar.set_postfix(metrics) - else: - for epoch in range(self.max_epochs): - self._on_learn_epoch_start(epoch) - if isinstance(dataloader.sampler, DistributedSampler): - dataloader.sampler.set_epoch(epoch) - pbar = tqdm( - dataloader, - desc=f"Train epoch [{epoch+1}/{self.max_epochs}]", - disable=not is_rank_0(), - ) - for experience in pbar: - self._on_learn_batch_start() - experience.to_device(device) - metrics = self.training_step(experience) - self._on_learn_batch_end(metrics, experience) - pbar.set_postfix(metrics) - self._on_learn_epoch_end(epoch) - - def fit( - self, - prompt_dataloader, - pretrain_dataloader, - num_episodes: int = 50000, - max_timesteps: int = 500, - update_timesteps: int = 5000, - ) -> None: - time = 0 - self.pretrain_dataloader = pretrain_dataloader - self.prompt_dataloader = prompt_dataloader - self._on_fit_start() - for episode in range(num_episodes): - self._on_episode_start(episode) - for timestep in tqdm( - range(max_timesteps), - desc=f"Episode [{episode+1}/{num_episodes}]", - disable=not is_rank_0(), - ): - time += 1 - prompts = next(iter(self.prompt_dataloader)) - self._on_make_experience_start() - self.experience_maker.initial_model.to(torch.cuda.current_device()) - self.experience_maker.reward_model.to(torch.cuda.current_device()) - experience = self._make_experience(prompts) - self._on_make_experience_end(experience) - self.replay_buffer.append(experience) - if time % update_timesteps == 0: - self.experience_maker.initial_model.to("cpu") - self.experience_maker.reward_model.to("cpu") - self._learn() - self.replay_buffer.clear() - self._on_episode_end(episode) - self._on_fit_end() - - # TODO(ver217): maybe simplify these code using context - def _on_fit_start(self) -> None: - for callback in self.callbacks: - callback.on_fit_start() - - def _on_fit_end(self) -> None: - for callback in self.callbacks: - callback.on_fit_end() - - def _on_episode_start(self, episode: int) -> None: - for callback in self.callbacks: - callback.on_episode_start(episode) - - def _on_episode_end(self, episode: int) -> None: - for callback in self.callbacks: - callback.on_episode_end(episode) - - def _on_make_experience_start(self) -> None: - for callback in self.callbacks: - callback.on_make_experience_start() - - def _on_make_experience_end(self, experience: Experience) -> None: - for callback in self.callbacks: - callback.on_make_experience_end(experience) - - def _on_learn_epoch_start(self, epoch: int) -> None: - for callback in self.callbacks: - callback.on_learn_epoch_start(epoch) - - def _on_learn_epoch_end(self, epoch: int) -> None: - for callback in self.callbacks: - callback.on_learn_epoch_end(epoch) - - def _on_learn_batch_start(self) -> None: - for callback in self.callbacks: - callback.on_learn_batch_start() - - def _on_learn_batch_end(self, metrics: dict, experience: Experience) -> None: - for callback in self.callbacks: - callback.on_learn_batch_end(metrics, experience) diff --git a/neurons/text/prompting/miners/self_hosted/coati/trainer/callbacks/__init__.py b/neurons/text/prompting/miners/self_hosted/coati/trainer/callbacks/__init__.py deleted file mode 100644 index 29c8c4f00a..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/trainer/callbacks/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from .base import Callback -from .performance_evaluator import PerformanceEvaluator -from .save_checkpoint import SaveCheckpoint - -__all__ = ["Callback", "PerformanceEvaluator", "SaveCheckpoint"] diff --git a/neurons/text/prompting/miners/self_hosted/coati/trainer/callbacks/base.py b/neurons/text/prompting/miners/self_hosted/coati/trainer/callbacks/base.py deleted file mode 100644 index d5181175b3..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/trainer/callbacks/base.py +++ /dev/null @@ -1,39 +0,0 @@ -from abc import ABC - -from coati.experience_maker import Experience - - -class Callback(ABC): - """ - Base callback class. It defines the interface for callbacks. - """ - - def on_fit_start(self) -> None: - pass - - def on_fit_end(self) -> None: - pass - - def on_episode_start(self, episode: int) -> None: - pass - - def on_episode_end(self, episode: int) -> None: - pass - - def on_make_experience_start(self) -> None: - pass - - def on_make_experience_end(self, experience: Experience) -> None: - pass - - def on_learn_epoch_start(self, epoch: int) -> None: - pass - - def on_learn_epoch_end(self, epoch: int) -> None: - pass - - def on_learn_batch_start(self) -> None: - pass - - def on_learn_batch_end(self, metrics: dict, experience: Experience) -> None: - pass diff --git a/neurons/text/prompting/miners/self_hosted/coati/trainer/callbacks/performance_evaluator.py b/neurons/text/prompting/miners/self_hosted/coati/trainer/callbacks/performance_evaluator.py deleted file mode 100644 index 16c6bf12d5..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/trainer/callbacks/performance_evaluator.py +++ /dev/null @@ -1,161 +0,0 @@ -from time import time -from typing import Optional - -import torch -import torch.distributed as dist -from coati.experience_maker import Experience - -from .base import Callback - - -def get_world_size() -> int: - if dist.is_initialized(): - return dist.get_world_size() - return 1 - - -def print_rank_0(*args, **kwargs) -> None: - if not dist.is_initialized() or dist.get_rank() == 0: - print(*args, **kwargs) - - -@torch.no_grad() -def all_reduce_mean(x: float, world_size: int) -> float: - if world_size == 1: - return x - tensor = torch.tensor([x], device=torch.cuda.current_device()) - dist.all_reduce(tensor) - tensor = tensor / world_size - return tensor.item() - - -class PerformanceEvaluator(Callback): - """ - Callback for valuate the performance of the model. - Args: - actor_num_params: The number of parameters of the actor model. - critic_num_params: The number of parameters of the critic model. - initial_model_num_params: The number of parameters of the initial model. - reward_model_num_params: The number of parameters of the reward model. - enable_grad_checkpoint: Whether to enable gradient checkpointing. - ignore_episodes: The number of episodes to ignore when calculating the performance. - """ - - def __init__( - self, - actor_num_params: int, - critic_num_params: int, - initial_model_num_params: int, - reward_model_num_params: int, - enable_grad_checkpoint: bool = False, - ignore_episodes: int = 0, - ) -> None: - super().__init__() - self.world_size = get_world_size() - self.actor_num_params = actor_num_params - self.critic_num_params = critic_num_params - self.initial_model_num_params = initial_model_num_params - self.reward_model_num_params = reward_model_num_params - self.enable_grad_checkpoint = enable_grad_checkpoint - self.ignore_episodes = ignore_episodes - self.disable: bool = False - - self.make_experience_duration: float = 0.0 - self.make_experience_start_time: Optional[float] = None - self.make_experience_num_samples: int = 0 - self.make_experience_flop: int = 0 - self.learn_duration: float = 0.0 - self.learn_start_time: Optional[float] = None - self.learn_num_samples: int = 0 - self.learn_flop: int = 0 - - def on_episode_start(self, episode: int) -> None: - self.disable = self.ignore_episodes > 0 and episode < self.ignore_episodes - - def on_make_experience_start(self) -> None: - if self.disable: - return - self.make_experience_start_time = time() - - def on_make_experience_end(self, experience: Experience) -> None: - if self.disable: - return - self.make_experience_duration += time() - self.make_experience_start_time - - batch_size, seq_len = experience.sequences.shape - - self.make_experience_num_samples += batch_size - - # actor generate - num_actions = experience.action_mask.size(1) - input_len = seq_len - num_actions - total_seq_len = (input_len + seq_len - 1) * num_actions / 2 - self.make_experience_flop += ( - self.actor_num_params * batch_size * total_seq_len * 2 - ) - # actor forward - self.make_experience_flop += self.actor_num_params * batch_size * seq_len * 2 - # critic forward - self.make_experience_flop += self.critic_num_params * batch_size * seq_len * 2 - # initial model forward - self.make_experience_flop += ( - self.initial_model_num_params * batch_size * seq_len * 2 - ) - # reward model forward - self.make_experience_flop += ( - self.reward_model_num_params * batch_size * seq_len * 2 - ) - - def on_learn_batch_start(self) -> None: - if self.disable: - return - self.learn_start_time = time() - - def on_learn_batch_end(self, metrics: dict, experience: Experience) -> None: - if self.disable: - return - self.learn_duration += time() - self.learn_start_time - - batch_size, seq_len = experience.sequences.shape - - self.learn_num_samples += batch_size - - # actor forward-backward, 3 means forward(1) + backward(2) - self.learn_flop += ( - self.actor_num_params - * batch_size - * seq_len - * 2 - * (3 + int(self.enable_grad_checkpoint)) - ) - # critic foward-backward - self.learn_flop += ( - self.critic_num_params - * batch_size - * seq_len - * 2 - * (3 + int(self.enable_grad_checkpoint)) - ) - - def on_fit_end(self) -> None: - avg_make_experience_duration = all_reduce_mean( - self.make_experience_duration, self.world_size - ) - avg_learn_duration = all_reduce_mean(self.learn_duration, self.world_size) - - avg_make_experience_throughput = self.make_experience_num_samples / ( - avg_make_experience_duration + 1e-12 - ) - avg_make_experience_tflops = ( - self.make_experience_flop / 1e12 / (avg_make_experience_duration + 1e-12) - ) - - avg_learn_throughput = self.learn_num_samples / (avg_learn_duration + 1e-12) - avg_learn_tflops = self.learn_flop / 1e12 / (avg_learn_duration + 1e-12) - - print_rank_0( - f"Making experience throughput: {avg_make_experience_throughput:.3f} samples/sec, TFLOPS: {avg_make_experience_tflops:.3f}" - ) - print_rank_0( - f"Learning throughput: {avg_learn_throughput:.3f} samples/sec, TFLOPS: {avg_learn_tflops:.3f}" - ) diff --git a/neurons/text/prompting/miners/self_hosted/coati/trainer/callbacks/save_checkpoint.py b/neurons/text/prompting/miners/self_hosted/coati/trainer/callbacks/save_checkpoint.py deleted file mode 100644 index 8447c400ef..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/trainer/callbacks/save_checkpoint.py +++ /dev/null @@ -1,85 +0,0 @@ -import os - -import torch.distributed as dist -from coati.trainer.strategies import ColossalAIStrategy, Strategy -from coati.trainer.utils import is_rank_0 -from torch import nn -from torch.optim import Optimizer - -from .base import Callback - - -class SaveCheckpoint(Callback): - """ - The callback for saving checkpoint for coati. - - Only support saving actor and critic model. - A typical architecture of the saved checkpoint would be: - - checkpoint - - episode_x - - actor.pt - - actor-optim-rank-0.pt - - actor-optim-rank-1.pt - - critic.pt - - critic-optim-rank-0.pt - - critic-optim-rank-1.pt - - ... - - Args: - path(str): the base path you want to save checkpoint, the checkpoint would be saved at `path/checkpoint` - interval(int): the interval episode of saving checkpoint - strategy(Strategy): the strategy used to train - actor(nn.Module): the actor model - critic(nn.Module): the critic model - actor_optim(Optimizer): the optimizer of actor - critic_optim(Optimizer): the optimizer of critic - - """ - - def __init__( - self, - path: str, - interval: int, - strategy: Strategy, - actor: nn.Module = None, - critic: nn.Module = None, - actor_optim: Optimizer = None, - critic_optim: Optimizer = None, - ) -> None: - super().__init__() - self.path = os.path.join(path, "checkpoint") - self.interval = interval - self.strategy = strategy - self.model_dict = { - "actor": [actor, actor_optim], - "critic": [critic, critic_optim], - } - - def on_episode_end(self, episode: int) -> None: - if (episode + 1) % self.interval != 0: - return - base_path = os.path.join(self.path, f"episode_{episode}") - if not os.path.exists(base_path): - os.makedirs(base_path) - - for model in self.model_dict.keys(): - # save model - if self.model_dict[model][0] is None: - # saving only optimizer states is meaningless, so it would be skipped - continue - model_path = os.path.join(base_path, f"{model}.pt") - self.strategy.save_model( - model=self.model_dict[model][0], path=model_path, only_rank0=True - ) - - # save optimizer - if self.model_dict[model][1] is None: - continue - only_rank0 = not isinstance(self.strategy, ColossalAIStrategy) - rank = 0 if is_rank_0() else dist.get_rank() - optim_path = os.path.join(base_path, f"{model}-optim-rank-{rank}.pt") - self.strategy.save_optimizer( - optimizer=self.model_dict[model][1], - path=optim_path, - only_rank0=only_rank0, - ) diff --git a/neurons/text/prompting/miners/self_hosted/coati/trainer/ppo.py b/neurons/text/prompting/miners/self_hosted/coati/trainer/ppo.py deleted file mode 100644 index 52f6a4f6c4..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/trainer/ppo.py +++ /dev/null @@ -1,180 +0,0 @@ -from typing import Any, Callable, Dict, List, Optional - -import torch -import torch.nn as nn -from coati.experience_maker import Experience, NaiveExperienceMaker -from coati.models.base import Actor, Critic -from coati.models.generation_utils import update_model_kwargs_fn -from coati.models.loss import PolicyLoss, ValueLoss -from coati.replay_buffer import NaiveReplayBuffer -from torch.optim import Optimizer -from transformers.tokenization_utils_base import PreTrainedTokenizerBase - -from .base import Trainer -from .callbacks import Callback -from .strategies import Strategy - - -class PPOTrainer(Trainer): - """ - Trainer for PPO algorithm. - - Args: - strategy (Strategy): the strategy to use for training - actor (Actor): the actor model in ppo algorithm - critic (Critic): the critic model in ppo algorithm - reward_model (nn.Module): the reward model in rlhf algorithm to make reward of sentences - initial_model (Actor): the initial model in rlhf algorithm to generate reference logits to limit the update of actor - actor_optim (Optimizer): the optimizer to use for actor model - critic_optim (Optimizer): the optimizer to use for critic model - kl_coef (float, defaults to 0.1): the coefficient of kl divergence loss - train_batch_size (int, defaults to 8): the batch size to use for training - buffer_limit (int, defaults to 0): the max_size limitaiton of replay buffer - buffer_cpu_offload (bool, defaults to True): whether to offload replay buffer to cpu - eps_clip (float, defaults to 0.2): the clip coefficient of policy loss - value_clip (float, defaults to 0.4): the clip coefficient of value loss - experience_batch_size (int, defaults to 8): the batch size to use for experience generation - max_epochs (int, defaults to 1): the number of epochs of training process - tokenier (Callable, optional): the tokenizer to use for tokenizing the input - sample_replay_buffer (bool, defaults to False): whether to sample from replay buffer - dataloader_pin_memory (bool, defaults to True): whether to pin memory for data loader - callbacks (List[Callback], defaults to []): the callbacks to call during training process - generate_kwargs (dict, optional): the kwargs to use while model generating - """ - - def __init__( - self, - strategy: Strategy, - actor: Actor, - critic: Critic, - reward_model: nn.Module, - initial_model: Actor, - actor_optim: Optimizer, - critic_optim: Optimizer, - kl_coef: float = 0.1, - ptx_coef: float = 0.9, - train_batch_size: int = 8, - buffer_limit: int = 0, - buffer_cpu_offload: bool = True, - eps_clip: float = 0.2, - value_clip: float = 0.4, - experience_batch_size: int = 8, - max_epochs: int = 1, - tokenizer: Optional[Callable[[Any], dict]] = None, - sample_replay_buffer: bool = False, - dataloader_pin_memory: bool = True, - callbacks: List[Callback] = [], - **generate_kwargs - ) -> None: - experience_maker = NaiveExperienceMaker( - actor, critic, reward_model, initial_model, kl_coef - ) - replay_buffer = NaiveReplayBuffer( - train_batch_size, buffer_limit, buffer_cpu_offload - ) - generate_kwargs = _set_default_generate_kwargs(strategy, generate_kwargs, actor) - super().__init__( - strategy, - experience_maker, - replay_buffer, - experience_batch_size, - max_epochs, - tokenizer, - sample_replay_buffer, - dataloader_pin_memory, - callbacks, - **generate_kwargs - ) - self.actor = actor - self.critic = critic - - self.actor_loss_fn = PolicyLoss(eps_clip) - self.critic_loss_fn = ValueLoss(value_clip) - self.ptx_loss_fn = nn.CrossEntropyLoss(ignore_index=-100) - self.ptx_coef = ptx_coef - self.actor_optim = actor_optim - self.critic_optim = critic_optim - - def training_step(self, experience: Experience) -> Dict[str, float]: - self.actor.train() - self.critic.train() - # policy loss - num_actions = experience.action_mask.size(1) - action_log_probs = self.actor( - experience.sequences, num_actions, attention_mask=experience.attention_mask - ) - actor_loss = self.actor_loss_fn( - action_log_probs, - experience.action_log_probs, - experience.advantages, - action_mask=experience.action_mask, - ) - - # ptx loss - if self.ptx_coef != 0: - ptx = next(iter(self.pretrain_dataloader))["input_ids"].to( - torch.cuda.current_device() - ) - label = next(iter(self.pretrain_dataloader))["labels"].to( - torch.cuda.current_device() - )[:, 1:] - attention_mask = next(iter(self.pretrain_dataloader))["attention_mask"].to( - torch.cuda.current_device() - ) - ptx_log_probs = self.actor.get_base_model()( - ptx, attention_mask=attention_mask - )["logits"][..., :-1, :] - ptx_loss = self.ptx_loss_fn( - ptx_log_probs.view(-1, ptx_log_probs.size(-1)), label.view(-1) - ) - actor_loss = ptx_loss * self.ptx_coef + actor_loss * (1 - self.ptx_coef) - - self.strategy.backward(actor_loss, self.actor, self.actor_optim) - self.strategy.optimizer_step(self.actor_optim) - self.actor_optim.zero_grad() - - # value loss - values = self.critic( - experience.sequences, - action_mask=experience.action_mask, - attention_mask=experience.attention_mask, - ) - critic_loss = self.critic_loss_fn( - values, - experience.values, - experience.reward, - action_mask=experience.action_mask, - ) - self.strategy.backward(critic_loss, self.critic, self.critic_optim) - self.strategy.optimizer_step(self.critic_optim) - self.critic_optim.zero_grad() - - return {"reward": experience.reward.mean().item()} - - -def _set_default_generate_kwargs( - strategy: Strategy, generate_kwargs: dict, actor: Actor -) -> None: - origin_model = strategy._unwrap_actor(actor) - new_kwargs = {**generate_kwargs} - # use huggingface models method directly - if "prepare_inputs_fn" not in generate_kwargs and hasattr( - origin_model, "prepare_inputs_for_generation" - ): - new_kwargs["prepare_inputs_fn"] = origin_model.prepare_inputs_for_generation - - if "update_model_kwargs_fn" not in generate_kwargs: - new_kwargs["update_model_kwargs_fn"] = update_model_kwargs_fn - - return new_kwargs - - -def save_model( - self, - path: str, - only_rank0: bool = False, - tokenizer: Optional[PreTrainedTokenizerBase] = None, -) -> None: - self.strategy.save_model( - model=self.actor, path=path, only_rank0=only_rank0, tokenizer=tokenizer - ) diff --git a/neurons/text/prompting/miners/self_hosted/coati/trainer/rm.py b/neurons/text/prompting/miners/self_hosted/coati/trainer/rm.py deleted file mode 100644 index 8d7ae91028..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/trainer/rm.py +++ /dev/null @@ -1,160 +0,0 @@ -from abc import ABC -from datetime import datetime -from typing import Optional - -import pandas as pd -import torch -import torch.distributed as dist -from torch.optim import Optimizer, lr_scheduler -from torch.utils.data import DataLoader, Dataset, DistributedSampler -from tqdm import tqdm -from transformers.tokenization_utils_base import PreTrainedTokenizerBase - -from .strategies import Strategy -from .utils import is_rank_0 - - -class RewardModelTrainer(ABC): - """ - Trainer to use while training reward model. - - Args: - model (torch.nn.Module): the model to train - strategy (Strategy): the strategy to use for training - optim(Optimizer): the optimizer to use for training - loss_fn (callable): the loss function to use for training - train_dataset (Dataset): the dataset to use for training - valid_dataset (Dataset): the dataset to use for validation - eval_dataset (Dataset): the dataset to use for evaluation - batch_size (int, defaults to 1): the batch size while training - max_epochs (int, defaults to 2): the number of epochs to train - """ - - def __init__( - self, - model, - strategy: Strategy, - optim: Optimizer, - loss_fn, - train_dataset: Dataset, - valid_dataset: Dataset, - eval_dataset: Dataset, - batch_size: int = 1, - max_epochs: int = 1, - ) -> None: - super().__init__() - self.strategy = strategy - self.epochs = max_epochs - train_sampler = None - - if dist.is_initialized() and dist.get_world_size() > 1: - train_sampler = DistributedSampler( - train_dataset, shuffle=True, seed=42, drop_last=True - ) - self.train_dataloader = DataLoader( - train_dataset, - shuffle=(train_sampler is None), - sampler=train_sampler, - batch_size=batch_size, - ) - self.valid_dataloader = DataLoader( - valid_dataset, batch_size=batch_size, shuffle=True - ) - self.eval_dataloader = DataLoader( - eval_dataset, batch_size=batch_size, shuffle=True - ) - - self.model = strategy.setup_model(model) - self.loss_fn = loss_fn - self.optimizer = strategy.setup_optimizer(optim, self.model) - self.scheduler = lr_scheduler.CosineAnnealingLR( - self.optimizer, self.train_dataloader.__len__() // 100 - ) - - def eval_acc(self, dataloader): - dist = 0 - on = 0 - cnt = 0 - self.model.eval() - with torch.no_grad(): - for chosen_ids, c_mask, reject_ids, r_mask in dataloader: - chosen_ids = chosen_ids.squeeze(1).to(torch.cuda.current_device()) - c_mask = c_mask.squeeze(1).to(torch.cuda.current_device()) - reject_ids = reject_ids.squeeze(1).to(torch.cuda.current_device()) - r_mask = r_mask.squeeze(1).to(torch.cuda.current_device()) - chosen_reward = self.model(chosen_ids, attention_mask=c_mask) - reject_reward = self.model(reject_ids, attention_mask=r_mask) - for i in range(len(chosen_reward)): - cnt += 1 - if chosen_reward[i] > reject_reward[i]: - on += 1 - dist += (chosen_reward - reject_reward).mean().item() - dist_mean = dist / len(dataloader) - acc = on / cnt - self.model.train() - return dist_mean, acc - - def fit(self): - time = datetime.now() - epoch_bar = tqdm( - range(self.epochs), desc="Train epoch", disable=not is_rank_0() - ) - for epoch in range(self.epochs): - step_bar = tqdm( - range(self.train_dataloader.__len__()), - desc="Train step of epoch %d" % epoch, - disable=not is_rank_0(), - ) - # train - self.model.train() - cnt = 0 - acc = 0 - dist = 0 - for chosen_ids, c_mask, reject_ids, r_mask in self.train_dataloader: - chosen_ids = chosen_ids.squeeze(1).to(torch.cuda.current_device()) - c_mask = c_mask.squeeze(1).to(torch.cuda.current_device()) - reject_ids = reject_ids.squeeze(1).to(torch.cuda.current_device()) - r_mask = r_mask.squeeze(1).to(torch.cuda.current_device()) - chosen_reward = self.model(chosen_ids, attention_mask=c_mask) - reject_reward = self.model(reject_ids, attention_mask=r_mask) - loss = self.loss_fn(chosen_reward, reject_reward) - self.strategy.backward(loss, self.model, self.optimizer) - self.strategy.optimizer_step(self.optimizer) - self.optimizer.zero_grad() - cnt += 1 - if cnt == 100: - self.scheduler.step() - dist, acc = self.eval_acc(self.valid_dataloader) - cnt = 0 - if is_rank_0(): - log = pd.DataFrame( - [[step_bar.n, loss.item(), dist, acc]], - columns=["step", "loss", "dist", "acc"], - ) - log.to_csv( - "log_%s.csv" % time, mode="a", header=False, index=False - ) - step_bar.update() - step_bar.set_postfix({"dist": dist, "acc": acc}) - - # eval - dist, acc = self.eval_acc(self.eval_dataloader) - if is_rank_0(): - log = pd.DataFrame( - [[step_bar.n, loss.item(), dist, acc]], - columns=["step", "loss", "dist", "acc"], - ) - log.to_csv("log.csv", mode="a", header=False, index=False) - epoch_bar.update() - step_bar.set_postfix({"dist": dist, "acc": acc}) - step_bar.close() - - def save_model( - self, - path: str, - only_rank0: bool = False, - tokenizer: Optional[PreTrainedTokenizerBase] = None, - ) -> None: - self.strategy.save_model( - model=self.model, path=path, only_rank0=only_rank0, tokenizer=tokenizer - ) diff --git a/neurons/text/prompting/miners/self_hosted/coati/trainer/sft.py b/neurons/text/prompting/miners/self_hosted/coati/trainer/sft.py deleted file mode 100644 index db6aa0daf5..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/trainer/sft.py +++ /dev/null @@ -1,172 +0,0 @@ -import math -import time -from abc import ABC -from typing import Optional - -import loralib as lora -import torch -import torch.distributed as dist -import wandb -from coati.models.loss import GPTLMLoss -from torch import nn -from torch.optim import Adam, Optimizer -from torch.optim.lr_scheduler import LambdaLR -from torch.utils.data import DataLoader -from torch.utils.data.distributed import DistributedSampler -from tqdm import tqdm -from transformers.tokenization_utils_base import PreTrainedTokenizerBase -from transformers.trainer import get_scheduler - -from colossalai.logging import get_dist_logger - -from .strategies import Strategy -from .utils import is_rank_0 - - -class SFTTrainer(ABC): - """ - Trainer to use while training reward model. - - Args: - model (torch.nn.Module): the model to train - strategy (Strategy): the strategy to use for training - optim(Optimizer): the optimizer to use for training - train_dataloader: the dataloader to use for training - eval_dataloader: the dataloader to use for evaluation - batch_size (int, defaults to 1): the batch size while training - max_epochs (int, defaults to 2): the number of epochs to train - optim_kwargs (dict, defaults to {'lr':1e-4}): the kwargs to use while initializing optimizer - """ - - def __init__( - self, - model, - strategy: Strategy, - optim: Optimizer, - train_dataloader: DataLoader, - eval_dataloader: DataLoader = None, - batch_size: int = 1, - max_epochs: int = 2, - accimulation_steps: int = 8, - ) -> None: - super().__init__() - self.strategy = strategy - self.epochs = max_epochs - self.train_dataloader = train_dataloader - self.eval_dataloader = eval_dataloader - - self.model = strategy.setup_model(model) - if "DDP" in str(self.strategy): - self.model = self.model.module - self.optimizer = strategy.setup_optimizer(optim, self.model) - - self.accimulation_steps = accimulation_steps - num_update_steps_per_epoch = len(train_dataloader) // self.accimulation_steps - max_steps = math.ceil(self.epochs * num_update_steps_per_epoch) - - self.scheduler = get_scheduler( - "cosine", - self.optimizer, - num_warmup_steps=math.ceil(max_steps * 0.03), - num_training_steps=max_steps, - ) - - def fit(self, logger, log_interval=10): - wandb.init( - project="Coati", name=time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) - ) - wandb.watch(self.model) - total_loss = 0 - # epoch_bar = tqdm(range(self.epochs), desc='Epochs', disable=not is_rank_0()) - step_bar = tqdm( - range(len(self.train_dataloader) // self.accimulation_steps * self.epochs), - desc=f"steps", - disable=not is_rank_0(), - ) - for epoch in range(self.epochs): - # process_bar = tqdm(range(len(self.train_dataloader)), desc=f'Train process for{epoch}', disable=not is_rank_0()) - # train - self.model.train() - for batch_id, batch in enumerate(self.train_dataloader): - prompt_ids = batch["input_ids"].to(torch.cuda.current_device()) - p_mask = batch["attention_mask"].to(torch.cuda.current_device()) - labels = batch["labels"].to(torch.cuda.current_device()) - # prompt_ids = prompt_ids.squeeze(1).cuda() - # p_mask = p_mask.squeeze(1).cuda() - # prompt_logits = self.model(prompt_ids, attention_mask=p_mask, labels=labels) - - outputs = self.model(prompt_ids, attention_mask=p_mask, labels=labels) - - loss = outputs.loss - prompt_logits = outputs.logits - - if loss >= 2.5: - logger.warning(f"batch_id:{batch_id}, abnormal loss: {loss}") - - loss = loss / self.accimulation_steps - - self.strategy.backward(loss, self.model, self.optimizer) - - total_loss += loss.item() - - # gradient accumulation - if (batch_id + 1) % self.accimulation_steps == 0: - self.strategy.optimizer_step(self.optimizer) - self.optimizer.zero_grad() - self.scheduler.step() - wandb.log( - { - "loss": total_loss / self.accimulation_steps, - "lr": self.scheduler.get_last_lr()[0], - "epoch": epoch, - "batch_id": batch_id, - } - ) - total_loss = 0 - step_bar.update() - - # if batch_id % log_interval == 0: - # logger.info(f'Train Epoch {epoch}/{self.epochs} Batch {batch_id} Rank {dist.get_rank()} loss {loss.item()}') - # wandb.log({"loss": loss.item()}) - - # process_bar.update() - - # eval - if self.eval_dataloader is not None: - self.model.eval() - with torch.no_grad(): - loss_sum = 0 - num_seen = 0 - for batch in self.eval_dataloader: - prompt_ids = batch["input_ids"].to(torch.cuda.current_device()) - p_mask = batch["attention_mask"].to(torch.cuda.current_device()) - labels = batch["labels"].to(torch.cuda.current_device()) - # prompt_ids = prompt_ids.squeeze(1).cuda() - # p_mask = p_mask.squeeze(1).cuda() - - outputs = self.model( - prompt_ids, attention_mask=p_mask, labels=labels - ) - loss = outputs.loss - # prompt_logits = outputs.logits - - loss_sum += loss.item() - num_seen += prompt_ids.size(0) - - loss_mean = loss_sum / num_seen - if dist.get_rank() == 0: - logger.info( - f"Eval Epoch {epoch}/{self.epochs} loss {loss_mean}" - ) - - # epoch_bar.update() - - def save_model( - self, - path: str, - only_rank0: bool = False, - tokenizer: Optional[PreTrainedTokenizerBase] = None, - ) -> None: - self.strategy.save_model( - model=self.model, path=path, only_rank0=only_rank0, tokenizer=tokenizer - ) diff --git a/neurons/text/prompting/miners/self_hosted/coati/trainer/strategies/__init__.py b/neurons/text/prompting/miners/self_hosted/coati/trainer/strategies/__init__.py deleted file mode 100644 index 515d77df3a..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/trainer/strategies/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from .base import Strategy -from .colossalai import ColossalAIStrategy -from .ddp import DDPStrategy -from .naive import NaiveStrategy - -__all__ = ["Strategy", "NaiveStrategy", "DDPStrategy", "ColossalAIStrategy"] diff --git a/neurons/text/prompting/miners/self_hosted/coati/trainer/strategies/base.py b/neurons/text/prompting/miners/self_hosted/coati/trainer/strategies/base.py deleted file mode 100644 index c7f022ab3a..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/trainer/strategies/base.py +++ /dev/null @@ -1,152 +0,0 @@ -from abc import ABC, abstractmethod -from contextlib import nullcontext -from typing import Any, List, Optional, Tuple, Union - -import numpy as np -import torch -import torch.nn as nn -from coati.models.base import LM, Actor, Critic, RewardModel -from coati.replay_buffer import ReplayBuffer -from torch.optim import Optimizer -from torch.utils.data import DataLoader -from transformers.tokenization_utils_base import PreTrainedTokenizerBase - -from .sampler import DistributedSampler - -ModelOptimPair = Tuple[nn.Module, Optimizer] -ModelOrModelOptimPair = Union[nn.Module, ModelOptimPair] - - -class Strategy(ABC): - """ - Base class for training strategies. - """ - - def __init__(self) -> None: - super().__init__() - self.setup_distributed() - - @abstractmethod - def backward( - self, loss: torch.Tensor, model: nn.Module, optimizer: Optimizer, **kwargs - ) -> None: - pass - - @abstractmethod - def optimizer_step(self, optimizer: Optimizer, **kwargs) -> None: - pass - - @abstractmethod - def setup_distributed(self) -> None: - pass - - @abstractmethod - def setup_model(self, model: nn.Module) -> nn.Module: - pass - - @abstractmethod - def setup_optimizer(self, optimizer: Optimizer, model: nn.Module) -> Optimizer: - pass - - @abstractmethod - def setup_dataloader( - self, replay_buffer: ReplayBuffer, pin_memory: bool = False - ) -> DataLoader: - pass - - def model_init_context(self): - return nullcontext() - - def prepare( - self, *models_or_model_optim_pairs: ModelOrModelOptimPair - ) -> Union[List[ModelOrModelOptimPair], ModelOrModelOptimPair]: - """Prepare models or model-optimizer-pairs based on each strategy. - - Example:: - >>> # when fine-tuning actor and critic - >>> (actor, actor_optim), (critic, critic_optim), reward_model, initial_model = strategy.prepare((actor, actor_optim), (critic, critic_optim), reward_model, initial_model) - >>> # or when training reward model - >>> (reward_model, reward_model_optim) = strategy.prepare((reward_model, reward_model_optim)) - >>> # or just inference - >>> actor, critic = strategy.prepare(actor, critic) - - Returns: - Union[List[ModelOrModelOptimPair], ModelOrModelOptimPair]: Models or model-optimizer-pairs in the original order. - """ - - def prepare_model(model: nn.Module): - if isinstance(model, Actor): - return Actor(self.setup_model(self._unwrap_model(model))) - return self.setup_model(self._unwrap_model(model)) - - rets = [] - for arg in models_or_model_optim_pairs: - if isinstance(arg, tuple): - assert ( - len(arg) == 2 - ), f'Expect (model, optimizer) pair, got a tuple with size "{len(arg)}"' - model, optimizer = arg - model = prepare_model(model) - optimizer = self.setup_optimizer(optimizer, self._unwrap_model(model)) - rets.append((model, optimizer)) - elif isinstance(arg, nn.Module): - rets.append(prepare_model(arg)) - else: - raise RuntimeError( - f"Expect model or (model, optimizer) pair, got {type(arg)}" - ) - - if len(rets) == 1: - return rets[0] - return rets - - @staticmethod - def _unwrap_model(model: nn.Module) -> nn.Module: - """Useful for saving state dict. As actor is wrapped by Actor class again in `prepare()`, we should unwrap it before saving. - - Args: - model (nn.Module): an actor or a critic - """ - if isinstance(model, Actor) or isinstance(model, LM): - return model.model - return model - - @staticmethod - def _unwrap_actor(actor: Actor) -> nn.Module: - """Get `actor.model` from a wrapped (by `prepare()`) actor. Useful for getting original huggingface model. - - Args: - actor (Actor): a wrapped actor - """ - return Strategy._unwrap_model(actor) - - @abstractmethod - def save_model( - self, - model: nn.Module, - path: str, - only_rank0: bool = False, - tokenizer: Optional[PreTrainedTokenizerBase] = None, - ) -> None: - pass - - @abstractmethod - def load_model( - self, model: nn.Module, path: str, map_location: Any = None, strict: bool = True - ) -> None: - pass - - @abstractmethod - def save_optimizer( - self, optimizer: Optimizer, path: str, only_rank0: bool = False - ) -> None: - pass - - @abstractmethod - def load_optimizer( - self, optimizer: Optimizer, path: str, map_location: Any = None - ) -> None: - pass - - def setup_sampler(self, dataset) -> DistributedSampler: - return DistributedSampler(dataset, 1, 0) diff --git a/neurons/text/prompting/miners/self_hosted/coati/trainer/strategies/colossalai.py b/neurons/text/prompting/miners/self_hosted/coati/trainer/strategies/colossalai.py deleted file mode 100644 index b69765dde8..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/trainer/strategies/colossalai.py +++ /dev/null @@ -1,244 +0,0 @@ -import warnings -from typing import Optional, Union - -import torch -import torch.distributed as dist -import torch.nn as nn -import torch.optim as optim -from coati.models.base import LM, Actor, RewardModel -from coati.models.lora import LoraLinear -from torch.optim import Optimizer -from transformers.modeling_utils import PreTrainedModel -from transformers.tokenization_utils_base import PreTrainedTokenizerBase - -import colossalai -from colossalai.logging import get_dist_logger -from colossalai.nn.optimizer import CPUAdam, HybridAdam -from colossalai.tensor import ProcessGroup, ShardSpec -from colossalai.utils import get_current_device -from colossalai.zero import ( - ColoInitContext, - ZeroDDP, - zero_model_wrapper, - zero_optim_wrapper, -) -from colossalai.zero.gemini.utils import get_static_torch_model - -from .base import Strategy -from .ddp import DDPStrategy - -logger = get_dist_logger(__name__) - - -class ColossalAIStrategy(DDPStrategy): - """ - The strategy for training with ColossalAI. - - Args: - stage(int): The stage to use in ZeRO. Choose in (1, 2, 3) - precision(str): The precision to use. Choose in ('fp32', 'fp16'). Stage 3 only supports fp16. - seed(int): The seed for the random number generator. - shard_init(bool): Whether to shard the model parameters during initialization. Only for ZeRO-3. - This is not compativle with `from_pretrained()`. We temporarily disable this and will support it in the future. - placement_policy(str): The placement policy for gemini. Choose in ('cpu', 'cuda') - If it is “cpu”, parameters, gradients and optimizer states will be offloaded to CPU, - If it is “cuda”, they will not be offloaded, which means max CUDA memory will be used. It is the fastest. - pin_memory(bool): Whether to pin the memory for the data loader. Only for ZeRO-3. - force_outputs_fp32(bool): Whether to force the outputs to be fp32. Only for ZeRO-3. - search_range_mb(int): The search range in MB for the chunk size. Only for ZeRO-3. - hidden_dim(optional, int): The hidden dimension for the gemini. Only for ZeRO-3. - min_chunk_size_mb(float): The minimum chunk size in MB. Only for ZeRO-3. - gpu_margin_mem_ratio(float): The margin memory ratio for the GPU. Only for ZeRO-3. - reduce_bugket_size(int): The reduce bucket size in bytes. Only for ZeRO-1 and ZeRO-2. - overlap_communication(bool): Whether to overlap communication and computation. Only for ZeRO-1 and ZeRO-2. - initial_scale(float): The initial scale for the optimizer. - growth_factor(float): The growth factor for the optimizer. - backoff_factor(float): The backoff factor for the optimizer. - growth_interval(int): The growth interval for the optimizer. - hysteresis(int): The hysteresis for the optimizer. - min_scale(float): The minimum scale for the optimizer. - max_scale(float): The maximum scale for the optimizer. - max_norm(float): The maximum norm for the optimizer. - norm_type(float): The norm type for the optimizer. - - """ - - def __init__( - self, - stage: int = 3, - precision: str = "fp16", - seed: int = 42, - shard_init: bool = False, # only for stage 3 - placement_policy: str = "cuda", - pin_memory: bool = True, # only for stage 3 - force_outputs_fp32: bool = False, # only for stage 3 - search_range_mb: int = 32, # only for stage 3 - hidden_dim: Optional[int] = None, # only for stage 3 - min_chunk_size_mb: float = 32, # only for stage 3 - gpu_margin_mem_ratio: float = 0.0, # only for stage 3 - reduce_bucket_size: int = 12 * 1024**2, # only for stage 1&2 - overlap_communication: bool = True, # only for stage 1&2 - initial_scale: float = 2**16, - growth_factor: float = 2, - backoff_factor: float = 0.5, - growth_interval: int = 1000, - hysteresis: int = 2, - min_scale: float = 1, - max_scale: float = 2**32, - max_norm: float = 0.0, - norm_type: float = 2.0, - ) -> None: - super().__init__(seed) - assert placement_policy in ( - "cpu", - "cuda", - ), f'Unsupported placement policy "{placement_policy}"' - assert precision in ("fp32", "fp16"), f'Unsupported precision "{precision}"' - self.stage = stage - # TODO(ver217): support shard_init when using from_pretrained() - if shard_init: - warnings.warn( - f"Shard init is not supported model.from_pretrained() yet. Please load weights after strategy.prepare()" - ) - if stage == 3 and precision == "fp32": - warnings.warn(f"Stage 3 only supports fp16. Precision is set to fp16.") - precision = "fp16" - self.precision = precision - self.shard_init = shard_init - self.gemini_config = dict( - device=get_current_device(), - placement_policy=placement_policy, - pin_memory=pin_memory, - force_outputs_fp32=force_outputs_fp32, - strict_ddp_mode=shard_init, - search_range_mb=search_range_mb, - hidden_dim=hidden_dim, - min_chunk_size_mb=min_chunk_size_mb, - ) - if stage == 3: - self.zero_optim_config = dict(gpu_margin_mem_ratio=gpu_margin_mem_ratio) - else: - self.zero_optim_config = dict( - reduce_bucket_size=reduce_bucket_size, - overlap_communication=overlap_communication, - cpu_offload=(placement_policy == "cpu"), - ) - self.optim_kwargs = dict( - initial_scale=initial_scale, - growth_factor=growth_factor, - backoff_factor=backoff_factor, - growth_interval=growth_interval, - hysteresis=hysteresis, - min_scale=min_scale, - max_scale=max_scale, - max_norm=max_norm, - norm_type=norm_type, - ) - - def setup_distributed(self) -> None: - colossalai.launch_from_torch({}, seed=self.seed) - - def model_init_context(self): - if self.stage == 3: - world_size = dist.get_world_size() - shard_pg = ProcessGroup(tp_degree=world_size) if self.shard_init else None - default_dist_spec = ( - ShardSpec([-1], [world_size]) if self.shard_init else None - ) - return ColoInitContext( - device=get_current_device(), - dtype=torch.half, - default_pg=shard_pg, - default_dist_spec=default_dist_spec, - ) - return super().model_init_context() - - def setup_model(self, model: nn.Module) -> nn.Module: - model = zero_model_wrapper( - model, zero_stage=self.stage, gemini_config=self.gemini_config - ) - - if self.stage != 3 and self.precision == "fp16": - model = model.half() - return model - - def setup_optimizer( - self, optimizer: optim.Optimizer, model: nn.Module - ) -> optim.Optimizer: - assert isinstance( - optimizer, (CPUAdam, HybridAdam) - ), f"Unsupported optimizer {type(optimizer)}" - return zero_optim_wrapper( - model, optimizer, optim_config=self.zero_optim_config, **self.optim_kwargs - ) - - def backward( - self, loss: torch.Tensor, model: nn.Module, optimizer: optim.Optimizer, **kwargs - ) -> None: - optimizer.backward(loss) - - def optimizer_step(self, optimizer: optim.Optimizer, **kwargs) -> None: - optimizer.step() - - @staticmethod - def _unwrap_actor(actor: Actor) -> nn.Module: - model: Union[nn.Module, ZeroDDP] = Strategy._unwrap_actor(actor) - if isinstance(model, ZeroDDP): - return model.module - return model - - def _unwrap_model(self, model: Union[nn.Module, ZeroDDP]) -> nn.Module: - if isinstance(model, ZeroDDP) and self.stage == 3: - logger.info(f"model type: {type(model)}, get static torch model") - model = get_static_torch_model(model) - logger.info(f"unwrapped_model type: {type(model)}") - - return super()._unwrap_model(model) - - def save_model( - self, - model: nn.Module, - path: str, - only_rank0: bool = True, - tokenizer: Optional[PreTrainedTokenizerBase] = None, - ) -> None: - if only_rank0 and dist.get_rank() != 0: - return None - unwrapped_model = self._unwrap_model(model) - # TODO : better way to get torch model from gemini model - # to get torch model from gemini model - - for module in unwrapped_model.modules(): - if isinstance(module, LoraLinear): - module.merge_weights = True - module.eval() - if isinstance(unwrapped_model, RewardModel): - state_dict = unwrapped_model.state_dict() - if only_rank0 and dist.get_rank() != 0: - return - torch.save(state_dict, path) - else: - try: - if isinstance(unwrapped_model, LM): - unwrapped_model = unwrapped_model.model - logger.info(f"Saving model to {path}", ranks=[0]) - unwrapped_model.save_pretrained(path) - logger.info(f"Model saved to {path} Successfully", ranks=[0]) - if tokenizer is not None: - logger.info(f"Saving tokenizer to {path}", ranks=[0]) - tokenizer.save_pretrained(path) - logger.info(f"Tokenizer saved to {path} Successfully", ranks=[0]) - except AttributeError: - state_dict = unwrapped_model.state_dict() - if only_rank0 and dist.get_rank() != 0: - return - torch.save(state_dict, path) - - def save_optimizer( - self, optimizer: Optimizer, path: str, only_rank0: bool = False - ) -> None: - if only_rank0: - raise RuntimeError( - f"Optimizer states are sharded when using ColossalAIStrategy. Only rank0 is not supported." - ) - torch.save(optimizer.state_dict(), path) diff --git a/neurons/text/prompting/miners/self_hosted/coati/trainer/strategies/ddp.py b/neurons/text/prompting/miners/self_hosted/coati/trainer/strategies/ddp.py deleted file mode 100644 index 39a2d5947e..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/trainer/strategies/ddp.py +++ /dev/null @@ -1,127 +0,0 @@ -from typing import Optional - -import os -import random - -import numpy as np -import torch -import torch.distributed as dist -import torch.nn as nn -from coati.models.base import LM, Actor, RewardModel -from coati.models.lora import LoraLinear -from coati.replay_buffer import ReplayBuffer -from torch.nn.parallel import DistributedDataParallel as DDP -from torch.optim import Optimizer -from torch.utils.data import DataLoader -from transformers.tokenization_utils_base import PreTrainedTokenizerBase - -from .base import Strategy -from .naive import NaiveStrategy -from .sampler import DistributedSampler - - -class DDPStrategy(NaiveStrategy): - """ - Strategy for distributed training using torch.distributed. - """ - - def __init__(self, seed: int = 42) -> None: - self.seed = seed - super().__init__() - - def setup_distributed(self) -> None: - try: - rank = int(os.environ["RANK"]) - local_rank = int(os.environ["LOCAL_RANK"]) - world_size = int(os.environ["WORLD_SIZE"]) - host = os.environ["MASTER_ADDR"] - port = int(os.environ["MASTER_PORT"]) - except KeyError as e: - raise RuntimeError( - f"Could not find {e} in the torch environment, visit https://www.colossalai.org/ for more information on launching with torch" - ) - dist.init_process_group( - "nccl", - init_method=f"tcp://[{host}]:{port}", - world_size=world_size, - rank=rank, - ) - self.set_seed(self.seed) - torch.cuda.set_device(local_rank) - - def set_seed(self, seed: int) -> None: - random.seed(seed) - np.random.seed(seed) - torch.manual_seed(seed) - - def setup_model(self, model: nn.Module) -> nn.Module: - device = torch.cuda.current_device() - return DDP(model, device_ids=[device]) - - def setup_dataloader( - self, replay_buffer: ReplayBuffer, pin_memory: bool = False - ) -> DataLoader: - # DDP only mode, replay buffers on each rank are different. - # sampler = DistributedSampler(replay_buffer, - # num_replicas=dist.get_world_size(), - # rank=dist.get_rank(), - # shuffle=True, - # seed=self.seed, - # drop_last=True) - return DataLoader( - replay_buffer, - batch_size=replay_buffer.sample_batch_size, - # sampler=sampler, - shuffle=True, - drop_last=True, - pin_memory=pin_memory, - collate_fn=replay_buffer.collate_fn, - ) - - @staticmethod - def _unwrap_actor(actor: Actor) -> nn.Module: - model: DDP = Strategy._unwrap_actor(actor) - return model.module - - def save_model( - self, - model: nn.Module, - path: str, - only_rank0: bool = False, - tokenizer: Optional[PreTrainedTokenizerBase] = None, - ) -> None: - if only_rank0 and dist.get_rank() != 0: - return None - - for module in model.modules(): - if isinstance(module, LoraLinear): - module.merge_weights = True - module.eval() - - if isinstance(model, RewardModel): - state_dict = model.state_dict() - if only_rank0 and dist.get_rank() != 0: - return - torch.save(state_dict, path) - else: - try: - if isinstance(model, LM): - model = model.model - model.save_pretrained(path) - if tokenizer is not None: - tokenizer.save_pretrained(path) - except AttributeError: - state_dict = model.state_dict() - if only_rank0 and dist.get_rank() != 0: - return - torch.save(state_dict, path) - - def save_optimizer( - self, optimizer: Optimizer, path: str, only_rank0: bool = False - ) -> None: - if only_rank0 and dist.get_rank() != 0: - return - super().save_optimizer(optimizer, path, only_rank0) - - def setup_sampler(self, dataset) -> DistributedSampler: - return DistributedSampler(dataset, dist.get_world_size(), dist.get_rank()) diff --git a/neurons/text/prompting/miners/self_hosted/coati/trainer/strategies/naive.py b/neurons/text/prompting/miners/self_hosted/coati/trainer/strategies/naive.py deleted file mode 100644 index 646f04a716..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/trainer/strategies/naive.py +++ /dev/null @@ -1,94 +0,0 @@ -from typing import Any, Optional - -import torch -import torch.nn as nn -import torch.optim as optim -from coati.replay_buffer import ReplayBuffer -from coati.models.base import LM, RewardModel -from coati.models.lora import LoraLinear -from torch.optim import Optimizer -from torch.utils.data import DataLoader -from transformers.tokenization_utils_base import PreTrainedTokenizerBase - -from .base import Strategy - - -class NaiveStrategy(Strategy): - """ - Strategy for single GPU. No parallelism is used. - """ - - def backward( - self, loss: torch.Tensor, model: nn.Module, optimizer: optim.Optimizer, **kwargs - ) -> None: - loss.backward() - - def optimizer_step(self, optimizer: optim.Optimizer, **kwargs) -> None: - optimizer.step() - - def setup_distributed(self) -> None: - pass - - def setup_model(self, model: nn.Module) -> nn.Module: - return model - - def setup_optimizer( - self, optimizer: optim.Optimizer, model: nn.Module - ) -> optim.Optimizer: - return optimizer - - def setup_dataloader( - self, replay_buffer: ReplayBuffer, pin_memory: bool = False - ) -> DataLoader: - return DataLoader( - replay_buffer, - batch_size=replay_buffer.sample_batch_size, - shuffle=True, - drop_last=True, - pin_memory=pin_memory, - collate_fn=replay_buffer.collate_fn, - ) - - def save_model( - self, - model: nn.Module, - path: str, - only_rank0: bool = False, - tokenizer: Optional[PreTrainedTokenizerBase] = None, - ) -> None: - for module in model.modules(): - if isinstance(module, LoraLinear): - module.merge_weights = True - module.eval() - - if isinstance(model, RewardModel): - state_dict = model.state_dict() - torch.save(state_dict, path) - else: - try: - if isinstance(model, LM): - model = model.model - model.save_pretrained(path) - if tokenizer is not None: - tokenizer.save_pretrained(path) - except AttributeError: - state_dict = model.state_dict() - torch.save(state_dict, path) - - def load_model( - self, model: nn.Module, path: str, map_location: Any = None, strict: bool = True - ) -> None: - unwrapped_model = self._unwrap_model(model) - state_dict = torch.load(path, map_location=map_location) - unwrapped_model.load_state_dict(state_dict, strict=strict) - - def save_optimizer( - self, optimizer: Optimizer, path: str, only_rank0: bool = False - ) -> None: - torch.save(optimizer.state_dict(), path) - - def load_optimizer( - self, optimizer: Optimizer, path: str, map_location: Any = None - ) -> None: - state_dict = torch.load(path, map_location=map_location) - optimizer.load_state_dict(state_dict) diff --git a/neurons/text/prompting/miners/self_hosted/coati/trainer/strategies/sampler.py b/neurons/text/prompting/miners/self_hosted/coati/trainer/strategies/sampler.py deleted file mode 100644 index 6e811bef11..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/trainer/strategies/sampler.py +++ /dev/null @@ -1,31 +0,0 @@ -import math - -import numpy as np - - -class DistributedSampler: - def __init__(self, dataset, num_replicas: int, rank: int) -> None: - self.dataset = dataset - self.num_replicas = num_replicas - self.rank = rank - - if len(self.dataset) % self.num_replicas != 0: - self.num_samples = math.ceil( - (len(self.dataset) - self.num_replicas) / self.num_replicas # type: ignore[arg-type] - ) - else: - self.num_samples = math.ceil(len(self.dataset) / self.num_replicas) - - self.total_size = self.num_samples * self.num_replicas - - indices = list(range(len(self.dataset))) - indices = indices[: self.total_size] - assert len(indices) == self.total_size - # subsample - indices = indices[self.rank : self.total_size : self.num_replicas] - assert len(indices) == self.num_samples - self.indices = indices - - def sample(self, batch_size: int) -> list: - sampled_indices = np.random.choice(self.indices, batch_size, replace=False) - return [self.dataset[idx] for idx in sampled_indices] diff --git a/neurons/text/prompting/miners/self_hosted/coati/trainer/utils.py b/neurons/text/prompting/miners/self_hosted/coati/trainer/utils.py deleted file mode 100644 index 6c9f7f085f..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/trainer/utils.py +++ /dev/null @@ -1,5 +0,0 @@ -import torch.distributed as dist - - -def is_rank_0() -> bool: - return not dist.is_initialized() or dist.get_rank() == 0 diff --git a/neurons/text/prompting/miners/self_hosted/coati/utils/__init__.py b/neurons/text/prompting/miners/self_hosted/coati/utils/__init__.py deleted file mode 100644 index 91f991a2ce..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/utils/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -from .tokenizer_utils import ( - prepare_llama_tokenizer_and_embedding, - smart_tokenizer_and_embedding_resize, -) - -__all__ = [ - "smart_tokenizer_and_embedding_resize", - "prepare_llama_tokenizer_and_embedding", -] diff --git a/neurons/text/prompting/miners/self_hosted/coati/utils/tokenizer_utils.py b/neurons/text/prompting/miners/self_hosted/coati/utils/tokenizer_utils.py deleted file mode 100644 index 8387936427..0000000000 --- a/neurons/text/prompting/miners/self_hosted/coati/utils/tokenizer_utils.py +++ /dev/null @@ -1,82 +0,0 @@ -# Copyright 2023 Rohan Taori, Ishaan Gulrajani, Tianyi Zhang, Yann Dubois, Xuechen Li -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from typing import Dict - -import transformers - -from ..models.llama.llama_lm import LlamaLM - -DEFAULT_PAD_TOKEN = "[PAD]" -DEFAULT_EOS_TOKEN = "" -DEFAULT_BOS_TOKEN = "" -DEFAULT_UNK_TOKEN = "" - - -def prepare_llama_tokenizer_and_embedding( - tokenizer: transformers.PreTrainedTokenizer, - model: transformers.PreTrainedModel, - special_tokens_dict: Dict = dict(pad_token=DEFAULT_PAD_TOKEN), -): - """prepare llama tokenizer and embedding.""" - - if tokenizer.pad_token is None: - smart_tokenizer_and_embedding_resize( - special_tokens_dict=dict(pad_token=DEFAULT_PAD_TOKEN), - tokenizer=tokenizer, - model=model, - ) - - tokenizer.add_special_tokens( - { - "eos_token": DEFAULT_EOS_TOKEN, - "bos_token": DEFAULT_BOS_TOKEN, - "unk_token": DEFAULT_UNK_TOKEN, - } - ) - - return tokenizer - - -def smart_tokenizer_and_embedding_resize( - tokenizer: transformers.PreTrainedTokenizer, - model: transformers.PreTrainedModel, - special_tokens_dict: Dict = dict(pad_token=DEFAULT_PAD_TOKEN), -): - """Resize tokenizer and embedding. - - Note: This is the unoptimized version that may make your embedding size not be divisible by 64. - """ - - if tokenizer.pad_token is None: - num_new_tokens = tokenizer.add_special_tokens(special_tokens_dict) - - if isinstance(model, LlamaLM): - model = model.get_base_model() - - model.model.resize_token_embeddings(len(tokenizer)) - - if num_new_tokens > 0: - input_embeddings = model.model.get_input_embeddings().weight.data - output_embeddings = model.model.get_output_embeddings().weight.data - - input_embeddings_avg = input_embeddings[:-num_new_tokens].mean( - dim=0, keepdim=True - ) - output_embeddings_avg = output_embeddings[:-num_new_tokens].mean( - dim=0, keepdim=True - ) - - input_embeddings[-num_new_tokens:] = input_embeddings_avg - output_embeddings[-num_new_tokens:] = output_embeddings_avg diff --git a/neurons/text/prompting/miners/self_hosted/neuron.py b/neurons/text/prompting/miners/self_hosted/neuron.py deleted file mode 100644 index d0ed45f88e..0000000000 --- a/neurons/text/prompting/miners/self_hosted/neuron.py +++ /dev/null @@ -1,266 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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. - -# General. -import os -import json -import time -import torch -import argparse -import bittensor - -from typing import List, Dict -from rich import print -from datetime import datetime - -# Torch tooling. -from torch.nn.utils.rnn import pad_sequence -from torch.optim import Adam -from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline - -# Coati PPO tooling. -from coati.models.auto import AutoActor as Actor, AutoCritic as Critic -from coati.trainer.strategies import ColossalAIStrategy, DDPStrategy, NaiveStrategy -from coati.models.loss import PolicyLoss, ValueLoss - - -# Check run config. -def check_config(config: "bittensor.Config"): - bittensor.logging.check_config(config) - bittensor.wallet.check_config(config) - bittensor.subtensor.check_config(config) - bittensor.metagraph.check_config(config) - bittensor.axon.check_config(config) - full_path = os.path.expanduser( - "{}/{}/{}/{}".format( - config.logging.logging_dir, - config.wallet.get("name", bittensor.defaults.wallet.name), - config.wallet.get("hotkey", bittensor.defaults.wallet.hotkey), - config.neuron.name, - ) - ) - config.neuron.full_path = os.path.expanduser(full_path) - if not os.path.exists(config.neuron.full_path): - os.makedirs(config.neuron.full_path) - - -# Create run config. -def get_config(): - parser = argparse.ArgumentParser() - parser.add_argument("--netuid", type=int, help="Subnet netuid", default=21) - parser.add_argument( - "--config", type=str, help="If set, defaults are overridden by passed file." - ) - parser.add_argument( - "--neuron.name", - type=str, - help="Trials for this miner go in miner.root / (wallet_cold - wallet_hot) / miner.name ", - default="robert_myers_prompting_miner", - ) - parser.add_argument( - "--neuron.blocks_per_epoch", - type=str, - help="Blocks until the miner sets weights on chain", - default=100, - ) - parser.add_argument( - "--neuron.no_set_weights", - action="store_true", - help="If True, the model does not set weights.", - default=False, - ) - parser.add_argument( - "--neuron.max_batch_size", - type=int, - help="The maximum batch size for forward requests.", - default=-1, - ) - parser.add_argument( - "--neuron.max_sequence_len", - type=int, - help="The maximum sequence length for forward requests.", - default=-1, - ) - parser.add_argument( - "--neuron.blacklist.hotkeys", - type=str, - required=False, - nargs="*", - action="store", - help="To blacklist certain hotkeys", - default=[], - ) - parser.add_argument( - "--neuron.lora_rank", type=int, help="The rank of the lora layer.", default=0 - ) - - bittensor.wallet.add_args(parser) - bittensor.axon.add_args(parser) - bittensor.subtensor.add_args(parser) - bittensor.logging.add_args(parser) - bittensor.metagraph.add_args(parser) - # bittensor.TextPromptingSynapse.add_args(parser) - return bittensor.config(parser) - - -# Main entry point for model serving. -def main(): - # --- Build, Check, Set and Print the run config. - config = get_config() - config.to_defaults() - check_config(config) - print(config) - - # --- PPO - strategy = NaiveStrategy() - - # --- Turn on logging. - bittensor.logging(config=config, logging_dir=config.neuron.full_path) - - # --- Create our chain connection. - subtensor = bittensor.subtensor(config) - - # --- Create our wallet and register it to the subnetwork. - wallet = bittensor.wallet(config) - wallet.register(netuid=config.netuid, subtensor=subtensor) - - # --- Create our network state cache - metagraph = bittensor.metagraph(config=config, netuid=config.netuid, sync=False) - metagraph.sync(netuid=config.netuid, subtensor=subtensor).save() - uid = metagraph.hotkeys.index(wallet.hotkey.ss58_address) - - # --- Build /Load our model and set the device. - with bittensor.__console__.status( - "Loading huggingface model robertmyers/bpt-sft ..." - ): - bittensor.logging.info("Loading", "robertmyers/bpt-sft") - tokenizer = AutoTokenizer.from_pretrained("robertmyers/bpt-sft") - actor = Actor( - pretrained="robertmyers/bpt-sft", lora_rank=config.neuron.lora_rank - ) - critic = Critic( - pretrained="robertmyers/bpt-sft", - lora_rank=config.neuron.lora_rank, - use_action_mask=True, - ) - actor_optim = Adam(actor.parameters(), lr=1e-7) - critic_optim = Adam(critic.parameters(), lr=1e-7) - # model = AutoModelForCausalLM.from_pretrained( "robertmyers/bpt-sft", torch_dtype=torch.float16 ) - - actor.to("cuda") - pipe = pipeline( - "text-generation", actor, tokenizer=tokenizer, device=0, max_new_tokens=256 - ) - - # --- Build axon server and start it.tensor.loggi - axon = bittensor.axon(wallet=wallet, metagraph=metagraph, config=config) - - def _process_history(history: List[str]) -> str: - processed_history = "" - for message in history: - message = json.loads(message) - if message["role"] == "system": - processed_history += "system: " + message["content"] + "\n" - - if message["role"] == "assistant": - processed_history += "assistant: " + message["content"] + "\n" - - if message["role"] == "user": - processed_history += "user: " + message["content"] + "\n" - return processed_history - - class Synapse(bittensor.TextPromptingSynapse): - def _priority( - self, forward_call: "bittensor.TextPromptingForwardCall" - ) -> float: - return 0.0 - - def _blacklist( - self, forward_call: "bittensor.TextPromptingForwardCall" - ) -> bool: - return False - - def backward( - self, - messages: List[Dict[str, str]], - response: str, - rewards: torch.FloatTensor, - ) -> str: - pass - - def forward(self, messages: List[str]) -> str: - history = _process_history(messages) - return ( - pipe(history)[0]["generated_text"] - .split(":")[-1] - .replace(str(history), "") - ) - - syn = Synapse() - axon.attach(syn) - axon.start() - axon.netuid = config.netuid - axon.protocol = 4 - subtensor.serve_axon(axon) - print(axon) - - # --- Run Forever. - last_update = subtensor.get_current_block() - while True: - # --- Wait until next epoch. - current_block = subtensor.get_current_block() - while (current_block - last_update) < config.neuron.blocks_per_epoch: - time.sleep(bittensor.__blocktime__) - current_block = subtensor.get_current_block() - last_update = subtensor.get_current_block() - - # --- Update the metagraph with the latest network state. - metagraph.sync(netuid=config.netuid, subtensor=subtensor) - uid = metagraph.hotkeys.index(wallet.hotkey.ss58_address) - - # --- Log performance. - print( - f"[white not bold]{datetime.now():%Y-%m-%d %H:%M:%S}[/white not bold]{' ' * 4} | " - f"{f'UID [bright_cyan]{uid}[/bright_cyan]'.center(16 + len('[bright_cyan][/bright_cyan]'))} | " - f"[dim white not bold] [green]{str(metagraph.S[uid].item()):.4}[/green] Stake [/dim white not bold]" - f"[dim white not bold]| [yellow]{str(metagraph.trust[uid].item()) :.3}[/yellow] Trust [/dim white not bold]" - f"[dim white not bold]| [green]{str(metagraph.incentive[uid].item()):.3}[/green] Incentive [/dim white not bold]" - ) - - # --- Set weights. - if not config.neuron.no_set_weights: - try: - # --- query the chain for the most current number of peers on the network - chain_weights = torch.zeros( - subtensor.subnetwork_n(netuid=config.netuid) - ) - chain_weights[uid] = 1 - did_set = subtensor.set_weights( - uids=torch.arange(0, len(chain_weights)), - netuid=config.netuid, - weights=chain_weights, - wait_for_inclusion=False, - wallet=wallet, - version_key=1, - ) - except: - pass - - -if __name__ == "__main__": - bittensor.utils.version_checking() - main() diff --git a/neurons/text/prompting/miners/self_hosted/ppo/__init__.py b/neurons/text/prompting/miners/self_hosted/ppo/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/neurons/text/prompting/miners/self_hosted/ppo/actor.py b/neurons/text/prompting/miners/self_hosted/ppo/actor.py deleted file mode 100644 index 8da4d94109..0000000000 --- a/neurons/text/prompting/miners/self_hosted/ppo/actor.py +++ /dev/null @@ -1,37 +0,0 @@ -from typing import Optional - -from transformers import AutoConfig, AutoModelForCausalLM - - -from base import Actor - - -class AutoActor(Actor): - """ - Auto Actor model. - - Args: - pretrained (str): Pretrained model name or path. - config (AutoConfig): Model config. - checkpoint (bool): Enable gradient checkpointing. - lora_rank (int): Rank of the low-rank approximation. - lora_train_bias (str): LoRA bias training mode. - """ - - def __init__( - self, - pretrained: Optional[str] = None, - config: Optional[AutoConfig] = None, - checkpoint: bool = False, - lora_rank: int = 0, - lora_train_bias: str = "none", - ) -> None: - if pretrained is not None: - model = AutoModelForCausalLM.from_pretrained(pretrained) - elif config is not None: - model = AutoModelForCausalLM(config) - else: - model = AutoModelForCausalLM(AutoConfig()) - if checkpoint: - model.gradient_checkpointing_enable() - super().__init__(model, lora_rank, lora_train_bias) diff --git a/neurons/text/prompting/miners/self_hosted/ppo/base/__init__.py b/neurons/text/prompting/miners/self_hosted/ppo/base/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/neurons/text/prompting/miners/self_hosted/ppo/base/actor.py b/neurons/text/prompting/miners/self_hosted/ppo/base/actor.py deleted file mode 100644 index 13e034aa48..0000000000 --- a/neurons/text/prompting/miners/self_hosted/ppo/base/actor.py +++ /dev/null @@ -1,76 +0,0 @@ -from typing import Optional, Tuple, Union - -import torch -import torch.nn as nn -import torch.nn.functional as F - -from ..generation import generate -from ..lora import LoRAModule -from ..utils import log_probs_from_logits - - -class Actor(LoRAModule): - """ - Actor model base class. - - Args: - model (nn.Module): Actor Model. - lora_rank (int): LoRA rank. - lora_train_bias (str): LoRA bias training mode. - """ - - def __init__( - self, model: nn.Module, lora_rank: int = 0, lora_train_bias: str = "none" - ) -> None: - super().__init__(lora_rank=lora_rank, lora_train_bias=lora_train_bias) - self.model = model - self.convert_to_lora() - - @torch.no_grad() - def generate( - self, input_ids: torch.Tensor, return_action_mask: bool = True, **kwargs - ) -> Union[ - Tuple[torch.LongTensor, torch.LongTensor], - Tuple[torch.LongTensor, torch.LongTensor, torch.BoolTensor], - ]: - sequences = generate(self.model, input_ids, **kwargs) - attention_mask = None - pad_token_id = kwargs.get("pad_token_id", None) - if pad_token_id is not None: - attention_mask = sequences.not_equal(pad_token_id).to( - dtype=torch.long, device=sequences.device - ) - if not return_action_mask: - return sequences, attention_mask, None - input_len = input_ids.size(1) - eos_token_id = kwargs.get("eos_token_id", None) - if eos_token_id is None: - action_mask = torch.ones_like(sequences, dtype=torch.bool) - else: - # left padding may be applied, only mask action - action_mask = (sequences[:, input_len:] == eos_token_id).cumsum(dim=-1) == 0 - action_mask = F.pad( - action_mask, (1 + input_len, -1), value=True - ) # include eos token and input - action_mask[:, :input_len] = False - action_mask = action_mask[:, 1:] - return ( - sequences, - attention_mask, - action_mask[:, -(sequences.size(1) - input_len) :], - ) - - def forward( - self, - sequences: torch.LongTensor, - num_actions: int, - attention_mask: Optional[torch.Tensor] = None, - ) -> torch.Tensor: - """Returns action log probs""" - output = self.model(sequences, attention_mask=attention_mask) - logits = output["logits"] - log_probs = log_probs_from_logits(logits[:, :-1, :], sequences[:, 1:]) - return log_probs[:, -num_actions:] - - def get_base_model(self): - return self.model diff --git a/neurons/text/prompting/miners/self_hosted/ppo/base/critic.py b/neurons/text/prompting/miners/self_hosted/ppo/base/critic.py deleted file mode 100644 index 9a047ce84e..0000000000 --- a/neurons/text/prompting/miners/self_hosted/ppo/base/critic.py +++ /dev/null @@ -1,55 +0,0 @@ -from typing import Optional - -import torch -import torch.nn as nn - -from ..lora import LoRAModule -from ..utils import masked_mean - - -class Critic(LoRAModule): - """ - Critic model base class. - - Args: - model (nn.Module): Critic model. - value_head (nn.Module): Value head to get value. - lora_rank (int): LoRA rank. - lora_train_bias (str): LoRA bias training mode. - """ - - def __init__( - self, - model: nn.Module, - value_head: nn.Module, - lora_rank: int = 0, - lora_train_bias: str = "none", - use_action_mask: bool = False, - ) -> None: - super().__init__(lora_rank=lora_rank, lora_train_bias=lora_train_bias) - self.model = model - self.value_head = value_head - self.use_action_mask = use_action_mask - self.convert_to_lora() - - def forward( - self, - sequences: torch.LongTensor, - action_mask: Optional[torch.Tensor] = None, - attention_mask: Optional[torch.Tensor] = None, - ) -> torch.Tensor: - outputs = self.model(sequences, attention_mask=attention_mask) - last_hidden_states = outputs["last_hidden_state"] - - values = self.value_head(last_hidden_states).squeeze(-1) - - if action_mask is not None and self.use_action_mask: - num_actions = action_mask.size(1) - prompt_mask = attention_mask[:, :-num_actions] - values = values[:, :-num_actions] - value = masked_mean(values, prompt_mask, dim=1) - return value - - values = values[:, :-1] - value = values.mean(dim=1) - return value diff --git a/neurons/text/prompting/miners/self_hosted/ppo/base/lm.py b/neurons/text/prompting/miners/self_hosted/ppo/base/lm.py deleted file mode 100644 index f1ffc04fe7..0000000000 --- a/neurons/text/prompting/miners/self_hosted/ppo/base/lm.py +++ /dev/null @@ -1,35 +0,0 @@ -from typing import Optional, Tuple, Union - -import torch -import torch.nn as nn -import torch.nn.functional as F - -from ..generation import generate -from .actor import Actor - - -class LM(Actor): - """ - Language model base class. - - Args: - model (nn.Module): Language Model. - lora_rank (int): LoRA rank. - lora_train_bias (str): LoRA bias training mode. - """ - - def __init__( - self, model: nn.Module, lora_rank: int = 0, lora_train_bias: str = "none" - ) -> None: - super().__init__( - model=model, lora_rank=lora_rank, lora_train_bias=lora_train_bias - ) - - def forward( - self, sequences: torch.LongTensor, attention_mask: Optional[torch.Tensor] = None - ) -> torch.Tensor: - """Returns output log probs""" - output = self.model(sequences, attention_mask=attention_mask) - logits = output["logits"] - log_probs = F.log_softmax(logits, dim=-1) - return log_probs diff --git a/neurons/text/prompting/miners/self_hosted/ppo/base/reward_model.py b/neurons/text/prompting/miners/self_hosted/ppo/base/reward_model.py deleted file mode 100644 index 0b18feaa47..0000000000 --- a/neurons/text/prompting/miners/self_hosted/ppo/base/reward_model.py +++ /dev/null @@ -1,47 +0,0 @@ -from typing import Optional - -import torch -import torch.nn as nn - -from ..lora import LoRAModule - - -class RewardModel(LoRAModule): - """ - Reward model base class. - - Args: - model (nn.Module): Reward model. - value_head (nn.Module): Value head to get reward score. - lora_rank (int): LoRA rank. - lora_train_bias (str): LoRA bias training mode. - """ - - def __init__( - self, - model: nn.Module, - value_head: Optional[nn.Module] = None, - lora_rank: int = 0, - lora_train_bias: str = "none", - ) -> None: - super().__init__(lora_rank=lora_rank, lora_train_bias=lora_train_bias) - self.model = model - self.convert_to_lora() - - if value_head is not None: - if value_head.out_features != 1: - raise ValueError( - "The value head of reward model's output dim should be 1!" - ) - self.value_head = value_head - else: - self.value_head = nn.Linear(model.config.n_embd, 1) - - def forward( - self, sequences: torch.LongTensor, attention_mask: Optional[torch.Tensor] = None - ) -> torch.Tensor: - outputs = self.model(sequences, attention_mask=attention_mask) - last_hidden_states = outputs["last_hidden_state"] - values = self.value_head(last_hidden_states)[:, :-1] - value = values.mean(dim=1).squeeze(1) # ensure shape is (B) - return value diff --git a/neurons/text/prompting/miners/self_hosted/ppo/generation.py b/neurons/text/prompting/miners/self_hosted/ppo/generation.py deleted file mode 100644 index 94a30f733a..0000000000 --- a/neurons/text/prompting/miners/self_hosted/ppo/generation.py +++ /dev/null @@ -1,167 +0,0 @@ -from typing import Any, Callable, Optional - -import torch -import torch.distributed as dist -import torch.nn as nn - -try: - from transformers.generation_logits_process import ( - LogitsProcessorList, - TemperatureLogitsWarper, - TopKLogitsWarper, - TopPLogitsWarper, - ) -except ImportError: - from transformers.generation import ( - LogitsProcessorList, - TemperatureLogitsWarper, - TopKLogitsWarper, - TopPLogitsWarper, - ) - - -def prepare_logits_processor( - top_k: Optional[int] = None, - top_p: Optional[float] = None, - temperature: Optional[float] = None, -) -> LogitsProcessorList: - processor_list = LogitsProcessorList() - if temperature is not None and temperature != 1.0: - processor_list.append(TemperatureLogitsWarper(temperature)) - if top_k is not None and top_k != 0: - processor_list.append(TopKLogitsWarper(top_k)) - if top_p is not None and top_p < 1.0: - processor_list.append(TopPLogitsWarper(top_p)) - return processor_list - - -def _is_sequence_finished(unfinished_sequences: torch.Tensor) -> bool: - if dist.is_initialized() and dist.get_world_size() > 1: - # consider DP - unfinished_sequences = unfinished_sequences.clone() - dist.all_reduce(unfinished_sequences) - return unfinished_sequences.max() == 0 - - -def sample( - model: nn.Module, - input_ids: torch.Tensor, - max_length: int, - early_stopping: bool = False, - eos_token_id: Optional[int] = None, - pad_token_id: Optional[int] = None, - top_k: Optional[int] = None, - top_p: Optional[float] = None, - temperature: Optional[float] = None, - prepare_inputs_fn: Optional[Callable[[torch.Tensor, Any], dict]] = None, - update_model_kwargs_fn: Optional[Callable[[dict, Any], dict]] = None, - **model_kwargs -) -> torch.Tensor: - if input_ids.size(1) >= max_length: - return input_ids - - logits_processor = prepare_logits_processor(top_k, top_p, temperature) - unfinished_sequences = input_ids.new(input_ids.shape[0]).fill_(1) - - for _ in range(input_ids.size(1), max_length): - model_inputs = ( - prepare_inputs_fn(input_ids, **model_kwargs) - if prepare_inputs_fn is not None - else {"input_ids": input_ids} - ) - outputs = model(**model_inputs) - - next_token_logits = outputs["logits"][:, -1, :] - # pre-process distribution - next_token_logits = logits_processor(input_ids, next_token_logits) - # sample - probs = torch.softmax(next_token_logits, dim=-1, dtype=torch.float) - next_tokens = torch.multinomial(probs, num_samples=1).squeeze(1) - - # finished sentences should have their next token be a padding token - if eos_token_id is not None: - if pad_token_id is None: - raise ValueError( - "If `eos_token_id` is defined, make sure that `pad_token_id` is defined." - ) - next_tokens = next_tokens * unfinished_sequences + pad_token_id * ( - 1 - unfinished_sequences - ) - - # update generated ids, model inputs for next step - input_ids = torch.cat([input_ids, next_tokens[:, None]], dim=-1) - if update_model_kwargs_fn is not None: - model_kwargs = update_model_kwargs_fn(outputs, **model_kwargs) - - # if eos_token was found in one sentence, set sentence to finished - if eos_token_id is not None: - unfinished_sequences = unfinished_sequences.mul( - (next_tokens != eos_token_id).long() - ) - - # stop when each sentence is finished if early_stopping=True - if early_stopping and _is_sequence_finished(unfinished_sequences): - break - - return input_ids - - -def generate( - model: nn.Module, - input_ids: torch.Tensor, - max_length: int, - num_beams: int = 1, - do_sample: bool = True, - early_stopping: bool = False, - eos_token_id: Optional[int] = None, - pad_token_id: Optional[int] = None, - top_k: Optional[int] = None, - top_p: Optional[float] = None, - temperature: Optional[float] = None, - prepare_inputs_fn: Optional[Callable[[torch.Tensor, Any], dict]] = None, - update_model_kwargs_fn: Optional[Callable[[dict, Any], dict]] = None, - **model_kwargs -) -> torch.Tensor: - """Generate token sequence. The returned sequence is input_ids + generated_tokens. - - Args: - model (nn.Module): model - input_ids (torch.Tensor): input sequence - max_length (int): max length of the returned sequence - num_beams (int, optional): number of beams. Defaults to 1. - do_sample (bool, optional): whether to do sample. Defaults to True. - early_stopping (bool, optional): if True, the sequence length may be smaller than max_length due to finding eos. Defaults to False. - eos_token_id (Optional[int], optional): end of sequence token id. Defaults to None. - pad_token_id (Optional[int], optional): pad token id. Defaults to None. - top_k (Optional[int], optional): the number of highest probability vocabulary tokens to keep for top-k-filtering. Defaults to None. - top_p (Optional[float], optional): If set to float < 1, only the smallest set of most probable tokens with probabilities that add up to top_p or higher are kept for generation. Defaults to None. - temperature (Optional[float], optional): The value used to module the next token probabilities. Defaults to None. - prepare_inputs_fn (Optional[Callable[[torch.Tensor, Any], dict]], optional): Function to preprocess model inputs. Arguments of this function should be input_ids and model_kwargs. Defaults to None. - update_model_kwargs_fn (Optional[Callable[[dict, Any], dict]], optional): Function to update model_kwargs based on outputs. Arguments of this function should be outputs and model_kwargs. Defaults to None. - """ - is_greedy_gen_mode = (num_beams == 1) and do_sample is False - is_sample_gen_mode = (num_beams == 1) and do_sample is True - is_beam_gen_mode = (num_beams > 1) and do_sample is False - if is_greedy_gen_mode: - # run greedy search - raise NotImplementedError - elif is_sample_gen_mode: - # run sample - return sample( - model, - input_ids, - max_length, - early_stopping=early_stopping, - eos_token_id=eos_token_id, - pad_token_id=pad_token_id, - top_k=top_k, - top_p=top_p, - temperature=temperature, - prepare_inputs_fn=prepare_inputs_fn, - update_model_kwargs_fn=update_model_kwargs_fn, - **model_kwargs - ) - elif is_beam_gen_mode: - raise NotImplementedError - else: - raise ValueError("Unsupported generation mode") diff --git a/neurons/text/prompting/miners/self_hosted/ppo/generation_utils.py b/neurons/text/prompting/miners/self_hosted/ppo/generation_utils.py deleted file mode 100644 index 1831deecfd..0000000000 --- a/neurons/text/prompting/miners/self_hosted/ppo/generation_utils.py +++ /dev/null @@ -1,102 +0,0 @@ -from typing import Optional - -import torch - - -def gpt_prepare_inputs_fn( - input_ids: torch.Tensor, past: Optional[torch.Tensor] = None, **kwargs -) -> dict: - token_type_ids = kwargs.get("token_type_ids", None) - # only last token for inputs_ids if past is defined in kwargs - if past: - input_ids = input_ids[:, -1].unsqueeze(-1) - if token_type_ids is not None: - token_type_ids = token_type_ids[:, -1].unsqueeze(-1) - - attention_mask = kwargs.get("attention_mask", None) - position_ids = kwargs.get("position_ids", None) - - if attention_mask is not None and position_ids is None: - # create position_ids on the fly for batch generation - position_ids = attention_mask.long().cumsum(-1) - 1 - position_ids.masked_fill_(attention_mask == 0, 1) - if past: - position_ids = position_ids[:, -1].unsqueeze(-1) - else: - position_ids = None - return { - "input_ids": input_ids, - "past_key_values": past, - "use_cache": kwargs.get("use_cache"), - "position_ids": position_ids, - "attention_mask": attention_mask, - "token_type_ids": token_type_ids, - } - - -def update_model_kwargs_fn(outputs: dict, **model_kwargs) -> dict: - if "past_key_values" in outputs: - model_kwargs["past"] = outputs["past_key_values"] - else: - model_kwargs["past"] = None - - # update token_type_ids with last value - if "token_type_ids" in model_kwargs: - token_type_ids = model_kwargs["token_type_ids"] - model_kwargs["token_type_ids"] = torch.cat( - [token_type_ids, token_type_ids[:, -1].unsqueeze(-1)], dim=-1 - ) - - # update attention mask - if "attention_mask" in model_kwargs: - attention_mask = model_kwargs["attention_mask"] - model_kwargs["attention_mask"] = torch.cat( - [attention_mask, attention_mask.new_ones((attention_mask.shape[0], 1))], - dim=-1, - ) - - return model_kwargs - - -def opt_prepare_inputs_fn( - input_ids: torch.Tensor, - past: Optional[torch.Tensor] = None, - attention_mask: Optional[torch.Tensor] = None, - use_cache: Optional[bool] = None, - **kwargs -) -> dict: - # if model is used as a decoder in encoder-decoder model, the decoder attention mask is created on the fly - if attention_mask is None: - attention_mask = input_ids.new_ones(input_ids.shape) - - if past: - input_ids = input_ids[:, -1:] - # first step, decoder_cached_states are empty - return { - "input_ids": input_ids, # encoder_outputs is defined. input_ids not needed - "attention_mask": attention_mask, - "past_key_values": past, - "use_cache": use_cache, - } - - -def bloom_prepare_inputs_fn( - input_ids: torch.Tensor, - past: Optional[torch.Tensor] = None, - attention_mask: Optional[torch.Tensor] = None, - use_cache: Optional[bool] = None, - **kwargs -) -> dict: - # if model is used as a decoder in encoder-decoder model, the decoder attention mask is created on the fly - if attention_mask is None: - attention_mask = input_ids.new_ones(input_ids.shape) - - if past: - input_ids = input_ids[:, -1:] - # first step, decoder_cached_states are empty - return { - "input_ids": input_ids, # encoder_outputs is defined. input_ids not needed - "attention_mask": attention_mask, - "past_key_values": past, - "use_cache": use_cache, - } diff --git a/neurons/text/prompting/miners/self_hosted/ppo/lora.py b/neurons/text/prompting/miners/self_hosted/ppo/lora.py deleted file mode 100644 index aa0f814838..0000000000 --- a/neurons/text/prompting/miners/self_hosted/ppo/lora.py +++ /dev/null @@ -1,135 +0,0 @@ -import math -from typing import Optional - -import loralib as lora -import torch -import torch.nn as nn -import torch.nn.functional as F - - -class LoraLinear(lora.LoRALayer, nn.Module): - """Replace in-place ops to out-of-place ops to fit gemini. Convert a torch.nn.Linear to LoraLinear.""" - - def __init__( - self, - weight: nn.Parameter, - bias: Optional[nn.Parameter], - r: int = 0, - lora_alpha: int = 1, - lora_dropout: float = 0.0, - fan_in_fan_out: bool = False, # Set this to True if the layer to replace stores weight like (fan_in, fan_out) - merge_weights: bool = True, - ): - nn.Module.__init__(self) - lora.LoRALayer.__init__( - self, - r=r, - lora_alpha=lora_alpha, - lora_dropout=lora_dropout, - merge_weights=merge_weights, - ) - self.weight = weight - self.bias = bias - - out_features, in_features = weight.shape - self.in_features = in_features - self.out_features = out_features - - self.fan_in_fan_out = fan_in_fan_out - # Actual trainable parameters - if r > 0: - self.lora_A = nn.Parameter(self.weight.new_zeros((r, in_features))) - self.lora_B = nn.Parameter(self.weight.new_zeros((out_features, r))) - self.scaling = self.lora_alpha / self.r - # Freezing the pre-trained weight matrix - self.weight.requires_grad = False - self.reset_parameters() - if fan_in_fan_out: - self.weight.data = self.weight.data.T - - def reset_parameters(self): - if hasattr(self, "lora_A"): - # initialize A the same way as the default for nn.Linear and B to zero - nn.init.kaiming_uniform_(self.lora_A, a=math.sqrt(5)) - nn.init.zeros_(self.lora_B) - - def train(self, mode: bool = True): - def T(w): - return w.T if self.fan_in_fan_out else w - - nn.Module.train(self, mode) - if self.merge_weights and self.merged: - # Make sure that the weights are not merged - if self.r > 0: - self.weight.data -= T(self.lora_B @ self.lora_A) * self.scaling - self.merged = False - - def eval(self): - def T(w): - return w.T if self.fan_in_fan_out else w - - nn.Module.eval(self) - if self.merge_weights and not self.merged: - # Merge the weights and mark it - if self.r > 0: - self.weight.data += T(self.lora_B @ self.lora_A) * self.scaling - delattr(self, "lora_A") - delattr(self, "lora_B") - self.merged = True - - def forward(self, x: torch.Tensor): - def T(w): - return w.T if self.fan_in_fan_out else w - - if self.r > 0 and not self.merged: - result = F.linear(x, T(self.weight), bias=self.bias) - if self.r > 0: - result = ( - result - + (self.lora_dropout(x) @ self.lora_A.t() @ self.lora_B.t()) - * self.scaling - ) - return result - else: - return F.linear(x, T(self.weight), bias=self.bias) - - -def lora_linear_wrapper(linear: nn.Linear, lora_rank: int) -> LoraLinear: - assert ( - lora_rank <= linear.in_features - ), f"LoRA rank ({lora_rank}) must be less than or equal to in features ({linear.in_features})" - lora_linear = LoraLinear( - linear.weight, linear.bias, r=lora_rank, merge_weights=False - ) - return lora_linear - - -def convert_to_lora_recursively(module: nn.Module, lora_rank: int) -> None: - for name, child in module.named_children(): - if isinstance(child, nn.Linear): - setattr(module, name, lora_linear_wrapper(child, lora_rank)) - else: - convert_to_lora_recursively(child, lora_rank) - - -class LoRAModule(nn.Module): - """A LoRA module base class. All derived classes should call `convert_to_lora()` at the bottom of `__init__()`. - This calss will convert all torch.nn.Linear layer to LoraLinear layer. - - Args: - lora_rank (int, optional): LoRA rank. 0 means LoRA is not applied. Defaults to 0. - lora_train_bias (str, optional): Whether LoRA train biases. - 'none' means it doesn't train biases. 'all' means it trains all biases. 'lora_only' means it only trains biases of LoRA layers. - Defaults to 'none'. - """ - - def __init__(self, lora_rank: int = 0, lora_train_bias: str = "none") -> None: - super().__init__() - self.lora_rank = lora_rank - self.lora_train_bias = lora_train_bias - - def convert_to_lora(self) -> None: - if self.lora_rank <= 0: - return - convert_to_lora_recursively(self, self.lora_rank) - lora.mark_only_lora_as_trainable(self, self.lora_train_bias) diff --git a/neurons/text/prompting/miners/self_hosted/ppo/loss.py b/neurons/text/prompting/miners/self_hosted/ppo/loss.py deleted file mode 100644 index 1bec7f84d2..0000000000 --- a/neurons/text/prompting/miners/self_hosted/ppo/loss.py +++ /dev/null @@ -1,138 +0,0 @@ -from typing import Optional - -import torch -import torch.nn as nn - -from utils import masked_mean - - -class GPTLMLoss(nn.Module): - """ - GPT Language Model Loss - """ - - def __init__(self): - super().__init__() - self.loss = nn.CrossEntropyLoss() - - def forward(self, logits: torch.Tensor, labels: torch.Tensor) -> torch.Tensor: - shift_logits = logits[..., :-1, :].contiguous() - shift_labels = labels[..., 1:].contiguous() - # Flatten the tokens - return self.loss( - shift_logits.view(-1, shift_logits.size(-1)), shift_labels.view(-1) - ) - - -class PolicyLoss(nn.Module): - """ - Policy Loss for PPO - """ - - def __init__(self, clip_eps: float = 0.2) -> None: - super().__init__() - self.clip_eps = clip_eps - - def forward( - self, - log_probs: torch.Tensor, - old_log_probs: torch.Tensor, - advantages: torch.Tensor, - action_mask: Optional[torch.Tensor] = None, - ) -> torch.Tensor: - ratio = (log_probs - old_log_probs).exp() - surr1 = ratio * advantages - surr2 = ratio.clamp(1 - self.clip_eps, 1 + self.clip_eps) * advantages - loss = -torch.min(surr1, surr2) - if action_mask is not None: - loss = masked_mean(loss, action_mask) - loss = loss.mean() - return loss - - -class ValueLoss(nn.Module): - """ - Value Loss for PPO - """ - - def __init__(self, clip_eps: float = 0.4) -> None: - super().__init__() - self.clip_eps = clip_eps - - def forward( - self, - values: torch.Tensor, - old_values: torch.Tensor, - reward: torch.Tensor, - action_mask: Optional[torch.Tensor] = None, - ) -> torch.Tensor: - values_clipped = old_values + (values - old_values).clamp( - -self.clip_eps, self.clip_eps - ) - surr1 = (values_clipped - reward) ** 2 - surr2 = (values - reward) ** 2 - loss = torch.max(surr1, surr2) - loss = loss.mean() - return loss - - -class PPOPtxActorLoss(nn.Module): - """ - To Do: - - PPO-ptx Actor Loss - """ - - def __init__( - self, - policy_clip_eps: float = 0.2, - pretrain_coef: float = 0.0, - pretrain_loss_fn=GPTLMLoss(), - ) -> None: - super().__init__() - self.pretrain_coef = pretrain_coef - self.policy_loss_fn = PolicyLoss(clip_eps=policy_clip_eps) - self.pretrain_loss_fn = pretrain_loss_fn - - def forward( - self, - log_probs: torch.Tensor, - old_log_probs: torch.Tensor, - advantages: torch.Tensor, - lm_logits: torch.Tensor, - lm_input_ids: torch.Tensor, - action_mask: Optional[torch.Tensor] = None, - ) -> torch.Tensor: - policy_loss = self.policy_loss_fn( - log_probs, old_log_probs, advantages, action_mask=action_mask - ) - lm_loss = self.pretrain_loss_fn(lm_logits, lm_input_ids) - return policy_loss + self.pretrain_coef * lm_loss - - -class LogSigLoss(nn.Module): - """ - Pairwise Loss for Reward Model - Details: https://arxiv.org/abs/2203.02155 - """ - - def forward( - self, chosen_reward: torch.Tensor, reject_reward: torch.Tensor - ) -> torch.Tensor: - probs = torch.sigmoid(chosen_reward - reject_reward) - log_probs = torch.log(probs) - loss = -log_probs.mean() - return loss - - -class LogExpLoss(nn.Module): - """ - Pairwise Loss for Reward Model - Details: https://arxiv.org/abs/2204.05862 - """ - - def forward( - self, chosen_reward: torch.Tensor, reject_reward: torch.Tensor - ) -> torch.Tensor: - loss = torch.log(1 + torch.exp(reject_reward - chosen_reward)).mean() - return loss diff --git a/neurons/text/prompting/miners/self_hosted/ppo/strategies/__init__.py b/neurons/text/prompting/miners/self_hosted/ppo/strategies/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/neurons/text/prompting/miners/self_hosted/ppo/strategies/base.py b/neurons/text/prompting/miners/self_hosted/ppo/strategies/base.py deleted file mode 100644 index 847fba1d81..0000000000 --- a/neurons/text/prompting/miners/self_hosted/ppo/strategies/base.py +++ /dev/null @@ -1,152 +0,0 @@ -from abc import ABC, abstractmethod -from contextlib import nullcontext -from typing import Any, List, Optional, Tuple, Union - -import numpy as np -import torch -import torch.nn as nn -from ..base import LM, Actor, Critic, RewardModel -from coati.replay_buffer import ReplayBuffer -from torch.optim import Optimizer -from torch.utils.data import DataLoader -from transformers.tokenization_utils_base import PreTrainedTokenizerBase - -from .sampler import DistributedSampler - -ModelOptimPair = Tuple[nn.Module, Optimizer] -ModelOrModelOptimPair = Union[nn.Module, ModelOptimPair] - - -class Strategy(ABC): - """ - Base class for training strategies. - """ - - def __init__(self) -> None: - super().__init__() - self.setup_distributed() - - @abstractmethod - def backward( - self, loss: torch.Tensor, model: nn.Module, optimizer: Optimizer, **kwargs - ) -> None: - pass - - @abstractmethod - def optimizer_step(self, optimizer: Optimizer, **kwargs) -> None: - pass - - @abstractmethod - def setup_distributed(self) -> None: - pass - - @abstractmethod - def setup_model(self, model: nn.Module) -> nn.Module: - pass - - @abstractmethod - def setup_optimizer(self, optimizer: Optimizer, model: nn.Module) -> Optimizer: - pass - - @abstractmethod - def setup_dataloader( - self, replay_buffer: ReplayBuffer, pin_memory: bool = False - ) -> DataLoader: - pass - - def model_init_context(self): - return nullcontext() - - def prepare( - self, *models_or_model_optim_pairs: ModelOrModelOptimPair - ) -> Union[List[ModelOrModelOptimPair], ModelOrModelOptimPair]: - """Prepare models or model-optimizer-pairs based on each strategy. - - Example:: - >>> # when fine-tuning actor and critic - >>> (actor, actor_optim), (critic, critic_optim), reward_model, initial_model = strategy.prepare((actor, actor_optim), (critic, critic_optim), reward_model, initial_model) - >>> # or when training reward model - >>> (reward_model, reward_model_optim) = strategy.prepare((reward_model, reward_model_optim)) - >>> # or just inference - >>> actor, critic = strategy.prepare(actor, critic) - - Returns: - Union[List[ModelOrModelOptimPair], ModelOrModelOptimPair]: Models or model-optimizer-pairs in the original order. - """ - - def prepare_model(model: nn.Module): - if isinstance(model, Actor): - return Actor(self.setup_model(self._unwrap_model(model))) - return self.setup_model(self._unwrap_model(model)) - - rets = [] - for arg in models_or_model_optim_pairs: - if isinstance(arg, tuple): - assert ( - len(arg) == 2 - ), f'Expect (model, optimizer) pair, got a tuple with size "{len(arg)}"' - model, optimizer = arg - model = prepare_model(model) - optimizer = self.setup_optimizer(optimizer, self._unwrap_model(model)) - rets.append((model, optimizer)) - elif isinstance(arg, nn.Module): - rets.append(prepare_model(arg)) - else: - raise RuntimeError( - f"Expect model or (model, optimizer) pair, got {type(arg)}" - ) - - if len(rets) == 1: - return rets[0] - return rets - - @staticmethod - def _unwrap_model(model: nn.Module) -> nn.Module: - """Useful for saving state dict. As actor is wrapped by Actor class again in `prepare()`, we should unwrap it before saving. - - Args: - model (nn.Module): an actor or a critic - """ - if isinstance(model, Actor) or isinstance(model, LM): - return model.model - return model - - @staticmethod - def _unwrap_actor(actor: Actor) -> nn.Module: - """Get `actor.model` from a wrapped (by `prepare()`) actor. Useful for getting original huggingface model. - - Args: - actor (Actor): a wrapped actor - """ - return Strategy._unwrap_model(actor) - - @abstractmethod - def save_model( - self, - model: nn.Module, - path: str, - only_rank0: bool = False, - tokenizer: Optional[PreTrainedTokenizerBase] = None, - ) -> None: - pass - - @abstractmethod - def load_model( - self, model: nn.Module, path: str, map_location: Any = None, strict: bool = True - ) -> None: - pass - - @abstractmethod - def save_optimizer( - self, optimizer: Optimizer, path: str, only_rank0: bool = False - ) -> None: - pass - - @abstractmethod - def load_optimizer( - self, optimizer: Optimizer, path: str, map_location: Any = None - ) -> None: - pass - - def setup_sampler(self, dataset) -> DistributedSampler: - return DistributedSampler(dataset, 1, 0) diff --git a/neurons/text/prompting/miners/self_hosted/ppo/strategies/naive.py b/neurons/text/prompting/miners/self_hosted/ppo/strategies/naive.py deleted file mode 100644 index 2898ea7847..0000000000 --- a/neurons/text/prompting/miners/self_hosted/ppo/strategies/naive.py +++ /dev/null @@ -1,94 +0,0 @@ -from typing import Any, Optional - -import torch -import torch.nn as nn -import torch.optim as optim -from coati.replay_buffer import ReplayBuffer -from coati.models.base import LM, RewardModel -from coati.models.lora import LoraLinear -from torch.optim import Optimizer -from torch.utils.data import DataLoader -from transformers.tokenization_utils_base import PreTrainedTokenizerBase - -from .base import Strategy - - -class NaiveStrategy(Strategy): - """ - Strategy for single GPU. No parallelism is used. - """ - - def backward( - self, loss: torch.Tensor, model: nn.Module, optimizer: optim.Optimizer, **kwargs - ) -> None: - loss.backward() - - def optimizer_step(self, optimizer: optim.Optimizer, **kwargs) -> None: - optimizer.step() - - def setup_distributed(self) -> None: - pass - - def setup_model(self, model: nn.Module) -> nn.Module: - return model - - def setup_optimizer( - self, optimizer: optim.Optimizer, model: nn.Module - ) -> optim.Optimizer: - return optimizer - - def setup_dataloader( - self, replay_buffer: ReplayBuffer, pin_memory: bool = False - ) -> DataLoader: - return DataLoader( - replay_buffer, - batch_size=replay_buffer.sample_batch_size, - shuffle=True, - drop_last=True, - pin_memory=pin_memory, - collate_fn=replay_buffer.collate_fn, - ) - - def save_model( - self, - model: nn.Module, - path: str, - only_rank0: bool = False, - tokenizer: Optional[PreTrainedTokenizerBase] = None, - ) -> None: - for module in model.modules(): - if isinstance(module, LoraLinear): - module.merge_weights = True - module.eval() - - if isinstance(model, RewardModel): - state_dict = model.state_dict() - torch.save(state_dict, path) - else: - try: - if isinstance(model, LM): - model = model.model - model.save_pretrained(path) - if tokenizer is not None: - tokenizer.save_pretrained(path) - except AttributeError: - state_dict = model.state_dict() - torch.save(state_dict, path) - - def load_model( - self, model: nn.Module, path: str, map_location: Any = None, strict: bool = True - ) -> None: - unwrapped_model = self._unwrap_model(model) - state_dict = torch.load(path, map_location=map_location) - unwrapped_model.load_state_dict(state_dict, strict=strict) - - def save_optimizer( - self, optimizer: Optimizer, path: str, only_rank0: bool = False - ) -> None: - torch.save(optimizer.state_dict(), path) - - def load_optimizer( - self, optimizer: Optimizer, path: str, map_location: Any = None - ) -> None: - state_dict = torch.load(path, map_location=map_location) - optimizer.load_state_dict(state_dict) diff --git a/neurons/text/prompting/miners/self_hosted/ppo/strategies/sampler.py b/neurons/text/prompting/miners/self_hosted/ppo/strategies/sampler.py deleted file mode 100644 index 0f6d8f6f1c..0000000000 --- a/neurons/text/prompting/miners/self_hosted/ppo/strategies/sampler.py +++ /dev/null @@ -1,31 +0,0 @@ -import math - -import numpy as np - - -class DistributedSampler: - def __init__(self, dataset, num_replicas: int, rank: int) -> None: - self.dataset = dataset - self.num_replicas = num_replicas - self.rank = rank - - if len(self.dataset) % self.num_replicas != 0: - self.num_samples = math.ceil( - (len(self.dataset) - self.num_replicas) / self.num_replicas # type: ignore[arg-type] - ) - else: - self.num_samples = math.ceil(len(self.dataset) / self.num_replicas) - - self.total_size = self.num_samples * self.num_replicas - - indices = list(range(len(self.dataset))) - indices = indices[: self.total_size] - assert len(indices) == self.total_size - # subsample - indices = indices[self.rank : self.total_size : self.num_replicas] - assert len(indices) == self.num_samples - self.indices = indices - - def sample(self, batch_size: int) -> list: - sampled_indices = np.random.choice(self.indices, batch_size, replace=False) - return [self.dataset[idx] for idx in sampled_indices] diff --git a/neurons/text/prompting/miners/self_hosted/ppo/utils.py b/neurons/text/prompting/miners/self_hosted/ppo/utils.py deleted file mode 100644 index 95bd586043..0000000000 --- a/neurons/text/prompting/miners/self_hosted/ppo/utils.py +++ /dev/null @@ -1,104 +0,0 @@ -from typing import Optional, Union - -import loralib as lora -import torch -import torch.nn as nn -import torch.nn.functional as F - - -def compute_approx_kl( - log_probs: torch.Tensor, - log_probs_base: torch.Tensor, - action_mask: Optional[torch.Tensor] = None, -) -> torch.Tensor: - """ - Compute the approximate KL divergence between two distributions. - Schulman blog: http://joschu.net/blog/kl-approx.html - - Args: - log_probs: Log probabilities of the new distribution. - log_probs_base: Log probabilities of the base distribution. - action_mask: Mask for actions. - """ - - log_ratio = log_probs - log_probs_base - approx_kl = (log_ratio.exp() - 1) - log_ratio - if action_mask is not None: - approx_kl = masked_mean(approx_kl, action_mask, dim=1) - return approx_kl - approx_kl = approx_kl.mean(dim=1) - return approx_kl - - -def compute_reward( - r: Union[torch.Tensor, float], - kl_coef: float, - log_probs: torch.Tensor, - log_probs_base: torch.Tensor, - action_mask: Optional[torch.Tensor] = None, -) -> torch.Tensor: - if kl_coef <= 0.0: - return r - kl = compute_approx_kl(log_probs, log_probs_base, action_mask=action_mask) - reward = r - kl_coef * kl - return reward - - -def log_probs_from_logits(logits: torch.Tensor, labels: torch.Tensor) -> torch.Tensor: - log_probs = F.log_softmax(logits, dim=-1) - log_probs_labels = log_probs.gather(dim=-1, index=labels.unsqueeze(-1)) - return log_probs_labels.squeeze(-1) - - -def masked_mean(tensor: torch.Tensor, mask: torch.Tensor, dim: int = 1) -> torch.Tensor: - tensor = tensor * mask - tensor = tensor.sum(dim=dim) - mask_sum = mask.sum(dim=dim) - mean = tensor / (mask_sum + 1e-8) - return mean - - -def masked_normalize( - tensor: torch.Tensor, mask: torch.Tensor, dim: int = 1, eps: float = 1e-8 -) -> torch.Tensor: - tensor = tensor * mask - mean = masked_mean(tensor, mask, dim=dim) - mean_centered = tensor - mean - var = masked_mean(mean_centered**2, mask, dim=dim) - return mean_centered * var.clamp(min=eps).rsqrt() - - -def normalize(tensor: torch.Tensor, dim: int = 0, eps: float = 1e-8) -> torch.Tensor: - mean = tensor.mean(dim) - mean_centered = tensor - mean - var = (mean_centered**2).mean(dim) - norm = mean_centered * var.clamp(min=eps).rsqrt() - return norm - - -def convert_to_lora( - model: nn.Module, - input_size: int, - output_size: int, - lora_rank: int = 16, - lora_alpha: int = 1, - lora_dropout: float = 0.0, - fan_in_fan_out: bool = False, - merge_weights: bool = True, -): - if lora_rank > min(input_size, output_size): - raise ValueError( - f"LoRA rank {lora_rank} must be less or equal than {min(input_size, output_size)}" - ) - - for name, module in model.named_modules(): - if isinstance(module, nn.Linear): - module._modules[name] = lora.Linear( - input_size, - output_size, - r=lora_rank, - lora_alpha=lora_alpha, - lora_dropout=lora_dropout, - fan_in_fan_out=fan_in_fan_out, - merge_weights=merge_weights, - ) diff --git a/neurons/text/prompting/miners/self_hosted/requirements.txt b/neurons/text/prompting/miners/self_hosted/requirements.txt deleted file mode 100644 index d3d7428067..0000000000 --- a/neurons/text/prompting/miners/self_hosted/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -loralib \ No newline at end of file diff --git a/requirements/dev.txt b/requirements/dev.txt index 71fde735a2..9a296221fb 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -5,3 +5,4 @@ pytest-rerunfailures==10.2 coveralls==3.3.1 pytest-cov==4.0.0 ddt==1.6.0 +hypothesis==6.81.1 diff --git a/requirements/prod.txt b/requirements/prod.txt index 661ed87d85..85dc066510 100644 --- a/requirements/prod.txt +++ b/requirements/prod.txt @@ -1,38 +1,34 @@ -ansible_vault==2.1 -argparse==1.4.0 -base58==2.0.1 -backoff==2.1.0 -bittensor-config>=0.0.0,<1.0.0 -bittensor-wallet==0.0.4,<1.0.0 -cryptography==41.0.0 -fuzzywuzzy==0.18.0 -grpcio==1.42.0 -grpcio-tools==1.42.0 -hypothesis==6.47.4 -idna<3,>=2.5 -jsonschema[format-nongpl]>=4.14.0,<=4.17.0 -langchain>=0.0.132,<=0.0.188 -loguru==0.6.0 -msgpack==1.0.4 -msgpack-numpy==0.4.7.1 +aiohttp==3.8.5 +ansible==6.7.0 +ansible_vault==2.1.0 +backoff +black==23.7.0 +cryptography==41.0.3 +ddt==1.6.0 +fuzzywuzzy>=0.18.0 +fastapi==0.99.1 +loguru==0.7.0 munch==2.5.0 -nest_asyncio==1.5.6 -netaddr==0.8.0 -numpy>=1.21.6,<=1.24.2 -pandas==1.5.2 -password_strength==0.0.3.post2 -prometheus_client==0.14.1 -protobuf==3.19.5 -psutil==5.9.4 -python-levenshtein==0.12.1 -pyyaml==6.0 -retry==0.9.2 -requests==2.25.0 -rich==12.5.1 +netaddr +numpy +msgpack +git+https://github.com/opentensor/msgpack-numpy.git#egg=msgpack-numpy +nest_asyncio +pycryptodome>=3.18.0,<4.0.0 +pyyaml +password_strength +pydantic!=1.8,!=1.8.1,<2.0.0,>=1.7.4 +PyNaCl>=1.3.0,<=1.5.0 +pytest-asyncio +python-Levenshtein +pytest +retry +requests +rich scalecodec==1.2.0 substrate-interface==1.5.0 -termcolor==2.1.1 -torch>=1.13.1,<=2.0.1 -transformers>=4.20.1,<=4.28.0 -tqdm==4.64.1 -wheel==0.37.1 \ No newline at end of file +termcolor +torch>=1.13.1 +tqdm +uvicorn==0.22.0 +wheel \ No newline at end of file diff --git a/scripts/build_protos.sh b/scripts/build_protos.sh deleted file mode 100755 index b9ee6b5c25..0000000000 --- a/scripts/build_protos.sh +++ /dev/null @@ -1 +0,0 @@ -python3 -m grpc.tools.protoc bittensor/_proto/bittensor.proto -I. --python_out=. --grpc_python_out=. diff --git a/scripts/check_compatibility.sh b/scripts/check_compatibility.sh index 3a6cf47e4c..5f48f4cbb0 100755 --- a/scripts/check_compatibility.sh +++ b/scripts/check_compatibility.sh @@ -17,6 +17,11 @@ check_compatibility() { all_supported=0 while read -r requirement; do + # Skip lines starting with git+ + if [[ "$requirement" == git+* ]]; then + continue + fi + package_name=$(echo "$requirement" | awk -F'[!=<>]' '{print $1}' | awk -F'[' '{print $1}') # Strip off brackets echo -n "Checking $package_name... " diff --git a/scripts/environments/apple_m1_environment.yml b/scripts/environments/apple_m1_environment.yml index 8830f3e339..0826ec38ff 100644 --- a/scripts/environments/apple_m1_environment.yml +++ b/scripts/environments/apple_m1_environment.yml @@ -53,7 +53,6 @@ dependencies: - libcxx=16.0.3=h4653b0c_0 - libffi=3.4.2=h3422bc3_5 - libgrpc=1.54.1=h9dbdbd0_0 - - libprotobuf=3.21.12=hb5ab8b9_0 - libsodium=1.0.18=h27ca646_1 - libsqlite=3.41.2=hb31c410_1 - libzlib=1.2.13=h03a7124_4 @@ -73,7 +72,6 @@ dependencies: - pickleshare=0.7.5=py_1003 - pip=23.1.2=pyhd8ed1ab_0 - pkgutil-resolve-name=1.3.10=pyhd8ed1ab_0 - - prometheus_client=0.17.0=pyhd8ed1ab_0 - prompt-toolkit=3.0.38=pyha770c72_0 - prompt_toolkit=3.0.38=hd8ed1ab_0 - ptyprocess=0.7.0=pyhd3deb0d_0 @@ -87,11 +85,9 @@ dependencies: - pysocks=1.7.1=pyha2e5f31_6 - python=3.10.10=h3ba56d0_0_cpython - python-dateutil=2.8.2=pyhd8ed1ab_0 - - python-fastjsonschema=2.17.1=pyhd8ed1ab_0 - python-json-logger=2.0.7=pyhd8ed1ab_0 - python_abi=3.10=3_cp310 - pytz=2023.3=pyhd8ed1ab_0 - - pyyaml=6.0=py310h8e9501a_5 - pyzmq=25.0.2=py310hc407298_0 - re2=2023.02.02=hb7217d7_0 - readline=8.2=h92ec313_1 @@ -128,7 +124,6 @@ dependencies: - arrow==1.2.3 - async-timeout==4.0.2 - backoff==2.1.0 - - base58==2.0.1 - blinker==1.6.2 - cachetools==4.2.4 - certifi==2020.11.8 @@ -139,7 +134,6 @@ dependencies: - cryptography==39.0.0 - cytoolz==0.12.1 - dataclasses-json==0.5.7 - - datasets==2.12.0 - dill==0.3.6 - distlib==0.3.6 - docker-pycreds==0.4.0 @@ -149,7 +143,6 @@ dependencies: - eth-typing==3.3.0 - eth-utils==2.1.0 - exceptiongroup==1.1.1 - - fastjsonschema==2.16.3 - filelock==3.12.0 - fqdn==1.5.1 - frozenlist==1.3.3 @@ -167,7 +160,6 @@ dependencies: - huggingface-hub==0.14.1 - hypothesis==6.47.4 - identify==2.5.24 - - idna==2.10 - ipykernel==6.23.0 - ipython-genutils==0.2.0 - ipywidgets==8.0.6 @@ -175,18 +167,14 @@ dependencies: - jinja2==3.0.0 - joblib==1.2.0 - jsonpointer==2.3 - - jsonschema==4.17.0 - jupyter==1.0.0 - jupyter-console==6.6.3 - jupyterlab-widgets==3.0.7 - - langchain==0.0.164 - loguru==0.6.0 - markupsafe==2.0.1 - marshmallow==3.19.0 - marshmallow-enum==1.5.1 - more-itertools==9.1.0 - - msgpack==1.0.4 - - msgpack-numpy==0.4.7.1 - multidict==6.0.4 - multiprocess==0.70.14 - munch==2.5.0 @@ -199,10 +187,8 @@ dependencies: - nodeenv==1.8.0 - notebook==6.5.4 - numexpr==2.8.4 - - numpy==1.21.6 - openapi-schema-pydantic==1.2.4 - openvalidators==1.0.1 - - pandas==1.5.2 - password-strength==0.0.3.post2 - pathtools==0.1.2 - pillow==9.5.0 @@ -211,8 +197,6 @@ dependencies: - pre-commit==3.3.2 - prometheus-client==0.14.1 - promise==2.3 - - protobuf==3.19.5 - - psutil==5.9.4 - py==1.11.0 - py-bip39-bindings==0.1.11 - py-ed25519-bindings==1.0.2 @@ -227,7 +211,6 @@ dependencies: - pympler==1.0.1 - pynacl==1.5.0 - pyparsing==3.0.9 - - python-levenshtein==0.12.1 - qqdm==0.0.7 - qtconsole==5.4.3 - qtpy==2.3.1 @@ -241,7 +224,6 @@ dependencies: - scalecodec==1.2.0 - scikit-learn==1.2.2 - scipy==1.10.1 - - sentence-transformers==2.2.2 - sentencepiece==0.1.97 - sentry-sdk==1.22.2 - setproctitle==1.3.2 @@ -262,7 +244,6 @@ dependencies: - torchvision==0.14.1 - tornado==6.3.1 - tqdm==4.64.1 - - transformers==4.20.1 - typing-extensions==4.5.0 - typing-inspect==0.8.0 - tzlocal==5.0.1 diff --git a/setup.py b/setup.py index 59f09c354c..17d3c6b6e8 100644 --- a/setup.py +++ b/setup.py @@ -23,13 +23,22 @@ import re import os import pathlib +import subprocess def read_requirements(path): + requirements = [] + git_requirements = [] + with pathlib.Path(path).open() as requirements_txt: - return [ - str(requirement) for requirement in parse_requirements(requirements_txt) - ] + for line in requirements_txt: + if line.startswith("git+"): + pkg_name = re.search(r"egg=([a-zA-Z0-9_-]+)", line.strip()).group(1) + requirements.append(pkg_name + " @ " + line.strip()) + else: + requirements.append(line.strip()) + + return requirements requirements = read_requirements("requirements/prod.txt") diff --git a/tests/__init__.py b/tests/__init__.py index 0756367eaf..1c7bc4757e 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,7 +1,7 @@ # The MIT License (MIT) # Copyright © 2022 Yuma Rao # Copyright © 2022-2023 Opentensor Foundation -# Copyright © 2023 Opentensor Technologies +# Copyright © 2023 Opentensor Technologies Inc # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated # documentation files (the “Software”), to deal in the Software without restriction, including without limitation diff --git a/tests/helpers/__init__.py b/tests/helpers/__init__.py index 0daa07c9a5..cdfa7e0bf1 100644 --- a/tests/helpers/__init__.py +++ b/tests/helpers/__init__.py @@ -1,5 +1,5 @@ # The MIT License (MIT) -# Copyright © 2023 Opentensor Technologies +# Copyright © 2023 Opentensor Technologies Inc # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated # documentation files (the “Software”), to deal in the Software without restriction, including without limitation @@ -22,4 +22,5 @@ _get_mock_wallet, CLOSE_IN_VALUE, MockConsole, + __mock_wallet_factory__, ) diff --git a/tests/helpers/helpers.py b/tests/helpers/helpers.py index 09c0b89c7a..482f59ce2d 100644 --- a/tests/helpers/helpers.py +++ b/tests/helpers/helpers.py @@ -16,17 +16,16 @@ # DEALINGS IN THE SOFTWARE. from typing import Union -from bittensor import Balance, NeuronInfo, axon_info, PrometheusInfo, __ss58_format__ +from bittensor import Balance, NeuronInfo, AxonInfo, PrometheusInfo, __ss58_format__ +from bittensor.mock.wallet_mock import MockWallet as _MockWallet +from bittensor.mock.wallet_mock import get_mock_coldkey as _get_mock_coldkey +from bittensor.mock.wallet_mock import get_mock_hotkey as _get_mock_hotkey +from bittensor.mock.wallet_mock import get_mock_keypair as _get_mock_keypair +from bittensor.mock.wallet_mock import get_mock_wallet as _get_mock_wallet + from rich.console import Console from rich.text import Text -from bittensor_wallet.mock import MockWallet as _MockWallet, utils as _mock_wallet_utils - -_get_mock_coldkey = _mock_wallet_utils.get_mock_coldkey -_get_mock_hotkey = _mock_wallet_utils.get_mock_hotkey -_get_mock_keypair = _mock_wallet_utils.get_mock_keypair -_get_mock_wallet = _mock_wallet_utils.get_mock_wallet - def __mock_wallet_factory__(*args, **kwargs) -> _MockWallet: """Returns a mock wallet object.""" @@ -68,7 +67,7 @@ def get_mock_neuron(**kwargs) -> NeuronInfo: mock_neuron_d = dict( { "netuid": -1, # mock netuid - "axon_info": axon_info( + "axon_info": AxonInfo( block=0, version=1, ip=0, @@ -136,6 +135,9 @@ def start(self): def stop(self): pass + def update(self, *args, **kwargs): + MockConsole().print(*args, **kwargs) + class MockConsole: """ diff --git a/tests/integration_tests/test_cli.py b/tests/integration_tests/test_cli.py index f86b152c83..ffc973d78c 100644 --- a/tests/integration_tests/test_cli.py +++ b/tests/integration_tests/test_cli.py @@ -1,6 +1,7 @@ # The MIT License (MIT) # Copyright © 2022 Yuma Rao # Copyright © 2022-2023 Opentensor Foundation +# Copyright © 2023 Opentensor Technologies Inc # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated # documentation files (the “Software”), to deal in the Software without restriction, including without limitation @@ -29,16 +30,16 @@ from substrateinterface.base import Keypair import bittensor -from bittensor.utils.balance import Balance +from bittensor.mock import MockSubtensor +from bittensor import Balance from tests.helpers import ( MockConsole, _get_mock_keypair, _get_mock_wallet as generate_wallet, ) -from bittensor._subtensor.subtensor_mock import MockSubtensor -_subtensor_mock: MockSubtensor = bittensor.subtensor(network="mock", _mock=True) +_subtensor_mock: MockSubtensor = MockSubtensor() def setUpModule(): @@ -58,6 +59,11 @@ def setUpModule(): _subtensor_mock.set_difficulty(netuid=3, difficulty=0) +def return_mock_sub(*args, **kwargs): + return MockSubtensor + + +@patch("bittensor.subtensor", new_callable=return_mock_sub) class TestCLIWithNetworkAndConfig(unittest.TestCase): def setUp(self): self._config = TestCLIWithNetworkAndConfig.construct_config() @@ -69,28 +75,35 @@ def config(self): @staticmethod def construct_config(): - defaults = bittensor.Config() + parser = bittensor.cli.__create_parser__() + defaults = bittensor.config(parser=parser, args=[]) + # Parse commands and subcommands + for command in bittensor.ALL_COMMANDS: + if ( + command in bittensor.ALL_COMMANDS + and "commands" in bittensor.ALL_COMMANDS[command] + ): + for subcommand in bittensor.ALL_COMMANDS[command]["commands"]: + defaults.merge( + bittensor.config(parser=parser, args=[command, subcommand]) + ) + else: + defaults.merge(bittensor.config(parser=parser, args=[command])) defaults.netuid = 1 - bittensor.subtensor.add_defaults(defaults) # Always use mock subtensor. defaults.subtensor.network = "finney" - defaults.subtensor._mock = True # Skip version checking. defaults.no_version_checking = True - bittensor.axon.add_defaults(defaults) - bittensor.wallet.add_defaults(defaults) - bittensor.dataset.add_defaults(defaults) - bittensor.logging.add_defaults(defaults) - bittensor.prometheus.add_defaults(defaults) return defaults - def test_overview(self): + def test_overview(self, _): config = self.config config.wallet.path = "/tmp/test_cli_test_overview" config.wallet.name = "mock_wallet" - config.command = "overview" + config.command = "wallet" + config.subcommand = "overview" config.no_prompt = True config.all = False config.netuid = [] # Don't set, so it tries all networks. @@ -159,7 +172,7 @@ def mock_get_wallet(*args, **kwargs): mock_console = MockConsole() with patch( - "bittensor._cli.commands.overview.get_hotkey_wallets_for_wallet" + "bittensor.commands.overview.get_hotkey_wallets_for_wallet" ) as mock_get_all_wallets: mock_get_all_wallets.return_value = mock_wallets with patch("bittensor.wallet") as mock_create_wallet: @@ -192,11 +205,12 @@ def mock_get_wallet(*args, **kwargs): if wallet not in [w for _, w in mock_registrations]: self.assertNotIn(wallet.hotkey_str, output_no_syntax) - def test_overview_not_in_first_subnet(self): + def test_overview_not_in_first_subnet(self, _): config = self.config config.wallet.path = "/tmp/test_cli_test_overview" config.wallet.name = "mock_wallet" - config.command = "overview" + config.command = "wallet" + config.subcommand = "overview" config.no_prompt = True config.all = False config.netuid = [] # Don't set, so it tries all networks. @@ -260,7 +274,7 @@ def mock_get_wallet(*args, **kwargs): mock_console = MockConsole() with patch( - "bittensor._cli.commands.overview.get_hotkey_wallets_for_wallet" + "bittensor.commands.overview.get_hotkey_wallets_for_wallet" ) as mock_get_all_wallets: mock_get_all_wallets.return_value = mock_wallets with patch("bittensor.wallet") as mock_create_wallet: @@ -294,26 +308,10 @@ def mock_get_wallet(*args, **kwargs): if wallet not in [w for _, w in mock_registrations]: self.assertNotIn(wallet.hotkey_str, output_no_syntax) - def test_overview_no_wallet(self): - # Mock IO for wallet - with patch( - "bittensor.Wallet.coldkeypub_file", - MagicMock(exists_on_device=MagicMock(return_value=False)), - ): - bittensor.subtensor.register = MagicMock(return_value=True) - - config = self.config - config.command = "overview" - config.no_prompt = True - config.all = False - config.netuid = [] # Don't set, so it tries all networks. - - cli = bittensor.cli(config) - cli.run() - - def test_overview_with_hotkeys_config(self): + def test_overview_with_hotkeys_config(self, _): config = self.config - config.command = "overview" + config.command = "wallet" + config.subcommand = "overview" config.no_prompt = True config.hotkeys = ["some_hotkey"] config.all = False @@ -322,9 +320,10 @@ def test_overview_with_hotkeys_config(self): cli = bittensor.cli(config) cli.run() - def test_overview_without_hotkeys_config(self): + def test_overview_without_hotkeys_config(self, _): config = self.config - config.command = "overview" + config.command = "wallet" + config.subcommand = "overview" config.no_prompt = True config.all = False config.netuid = [] # Don't set, so it tries all networks. @@ -332,9 +331,10 @@ def test_overview_without_hotkeys_config(self): cli = bittensor.cli(config) cli.run() - def test_overview_with_sort_by_config(self): + def test_overview_with_sort_by_config(self, _): config = self.config - config.command = "overview" + config.command = "wallet" + config.subcommand = "overview" config.no_prompt = True config.wallet.sort_by = "rank" config.all = False @@ -343,9 +343,10 @@ def test_overview_with_sort_by_config(self): cli = bittensor.cli(config) cli.run() - def test_overview_with_sort_by_bad_column_name(self): + def test_overview_with_sort_by_bad_column_name(self, _): config = self.config - config.command = "overview" + config.command = "wallet" + config.subcommand = "overview" config.no_prompt = True config.wallet.sort_by = "totallynotmatchingcolumnname" config.all = False @@ -354,9 +355,10 @@ def test_overview_with_sort_by_bad_column_name(self): cli = bittensor.cli(config) cli.run() - def test_overview_without_sort_by_config(self): + def test_overview_without_sort_by_config(self, _): config = self.config - config.command = "overview" + config.command = "wallet" + config.subcommand = "overview" config.no_prompt = True config.all = False config.netuid = [] # Don't set, so it tries all networks. @@ -364,9 +366,10 @@ def test_overview_without_sort_by_config(self): cli = bittensor.cli(config) cli.run() - def test_overview_with_sort_order_config(self): + def test_overview_with_sort_order_config(self, _): config = self.config - config.command = "overview" + config.command = "wallet" + config.subcommand = "overview" config.wallet.sort_order = "desc" # Set descending sort order config.no_prompt = True config.all = False @@ -375,9 +378,10 @@ def test_overview_with_sort_order_config(self): cli = bittensor.cli(config) cli.run() - def test_overview_with_sort_order_config_bad_sort_type(self): + def test_overview_with_sort_order_config_bad_sort_type(self, _): config = self.config - config.command = "overview" + config.command = "wallet" + config.subcommand = "overview" config.wallet.sort_order = "nowaythisshouldmatchanyorderingchoice" config.no_prompt = True config.all = False @@ -386,9 +390,10 @@ def test_overview_with_sort_order_config_bad_sort_type(self): cli = bittensor.cli(config) cli.run() - def test_overview_without_sort_order_config(self): + def test_overview_without_sort_order_config(self, _): config = self.config - config.command = "overview" + config.command = "wallet" + config.subcommand = "overview" # Don't specify sort_order in config config.no_prompt = True config.all = False @@ -397,9 +402,10 @@ def test_overview_without_sort_order_config(self): cli = bittensor.cli(config) cli.run() - def test_overview_with_width_config(self): + def test_overview_with_width_config(self, _): config = self.config - config.command = "overview" + config.command = "wallet" + config.subcommand = "overview" config.width = 100 config.no_prompt = True config.all = False @@ -408,9 +414,10 @@ def test_overview_with_width_config(self): cli = bittensor.cli(config) cli.run() - def test_overview_without_width_config(self): + def test_overview_without_width_config(self, _): config = self.config - config.command = "overview" + config.command = "wallet" + config.subcommand = "overview" # Don't specify width in config config.no_prompt = True config.all = False @@ -419,9 +426,10 @@ def test_overview_without_width_config(self): cli = bittensor.cli(config) cli.run() - def test_overview_all(self): + def test_overview_all(self, _): config = self.config - config.command = "overview" + config.command = "wallet" + config.subcommand = "overview" config.no_prompt = True config.netuid = [] # Don't set, so it tries all networks. @@ -429,9 +437,10 @@ def test_overview_all(self): cli = bittensor.cli(config) cli.run() - def test_unstake_with_specific_hotkeys(self): + def test_unstake_with_specific_hotkeys(self, _): config = self.config - config.command = "unstake" + config.command = "stake" + config.subcommand = "remove" config.no_prompt = True config.amount = 5.0 config.wallet.name = "fake_wallet" @@ -439,11 +448,11 @@ def test_unstake_with_specific_hotkeys(self): config.all_hotkeys = False # Notice no max_stake specified - mock_stakes: Dict[str, bittensor.Balance] = { + mock_stakes: Dict[str, Balance] = { # All have more than 5.0 stake - "hk0": bittensor.Balance.from_float(10.0), - "hk1": bittensor.Balance.from_float(11.1), - "hk2": bittensor.Balance.from_float(12.2), + "hk0": Balance.from_float(10.0), + "hk1": Balance.from_float(11.1), + "hk2": Balance.from_float(12.2), } mock_coldkey_kp = _get_mock_keypair(0, self.id()) @@ -504,9 +513,10 @@ def mock_get_wallet(*args, **kwargs): places=4, ) - def test_unstake_with_all_hotkeys(self): + def test_unstake_with_all_hotkeys(self, _): config = self.config - config.command = "unstake" + config.command = "stake" + config.subcommand = "remove" config.no_prompt = True config.amount = 5.0 config.wallet.name = "fake_wallet" @@ -514,11 +524,11 @@ def test_unstake_with_all_hotkeys(self): config.all_hotkeys = True # Notice no max_stake specified - mock_stakes: Dict[str, bittensor.Balance] = { + mock_stakes: Dict[str, Balance] = { # All have more than 5.0 stake - "hk0": bittensor.Balance.from_float(10.0), - "hk1": bittensor.Balance.from_float(11.1), - "hk2": bittensor.Balance.from_float(12.2), + "hk0": Balance.from_float(10.0), + "hk1": Balance.from_float(11.1), + "hk2": Balance.from_float(12.2), } mock_coldkey_kp = _get_mock_keypair(0, self.id()) @@ -555,7 +565,7 @@ def mock_get_wallet(*args, **kwargs): return mock_wallets[0] with patch( - "bittensor._cli.commands.unstake.get_hotkey_wallets_for_wallet" + "bittensor.commands.unstake.get_hotkey_wallets_for_wallet" ) as mock_get_all_wallets: mock_get_all_wallets.return_value = mock_wallets with patch("bittensor.wallet") as mock_create_wallet: @@ -583,20 +593,21 @@ def mock_get_wallet(*args, **kwargs): places=4, ) - def test_unstake_with_exclude_hotkeys_from_all(self): + def test_unstake_with_exclude_hotkeys_from_all(self, _): config = self.config - config.command = "unstake" + config.command = "stake" + config.subcommand = "remove" config.no_prompt = True config.amount = 5.0 config.wallet.name = "fake_wallet" config.hotkeys = ["hk1"] # Exclude hk1 config.all_hotkeys = True - mock_stakes: Dict[str, bittensor.Balance] = { + mock_stakes: Dict[str, Balance] = { # All have more than 5.0 stake - "hk0": bittensor.Balance.from_float(10.0), - "hk1": bittensor.Balance.from_float(11.1), - "hk2": bittensor.Balance.from_float(12.2), + "hk0": Balance.from_float(10.0), + "hk1": Balance.from_float(11.1), + "hk2": Balance.from_float(12.2), } mock_coldkey_kp = _get_mock_keypair(0, self.id()) @@ -633,7 +644,7 @@ def mock_get_wallet(*args, **kwargs): return mock_wallets[0] with patch( - "bittensor._cli.commands.unstake.get_hotkey_wallets_for_wallet" + "bittensor.commands.unstake.get_hotkey_wallets_for_wallet" ) as mock_get_all_wallets: mock_get_all_wallets.return_value = mock_wallets with patch("bittensor.wallet") as mock_create_wallet: @@ -667,9 +678,10 @@ def mock_get_wallet(*args, **kwargs): places=4, ) - def test_unstake_with_multiple_hotkeys_max_stake(self): + def test_unstake_with_multiple_hotkeys_max_stake(self, _): config = self.config - config.command = "unstake" + config.command = "stake" + config.subcommand = "remove" config.no_prompt = True # Notie amount is not specified config.max_stake = 5.0 # The keys should have at most 5.0 tao staked after @@ -677,11 +689,11 @@ def test_unstake_with_multiple_hotkeys_max_stake(self): config.hotkeys = ["hk0", "hk1", "hk2"] config.all_hotkeys = False - mock_stakes: Dict[str, bittensor.Balance] = { + mock_stakes: Dict[str, Balance] = { # All have more than 5.0 stake - "hk0": bittensor.Balance.from_float(10.0), - "hk1": bittensor.Balance.from_float(4.9), - "hk2": bittensor.Balance.from_float(12.2), + "hk0": Balance.from_float(10.0), + "hk1": Balance.from_float(4.9), + "hk2": Balance.from_float(12.2), } mock_coldkey_kp = _get_mock_keypair(0, self.id()) @@ -720,7 +732,7 @@ def mock_get_wallet(*args, **kwargs): return mock_wallets[0] with patch( - "bittensor._cli.commands.unstake.get_hotkey_wallets_for_wallet" + "bittensor.commands.unstake.get_hotkey_wallets_for_wallet" ) as mock_get_all_wallets: mock_get_all_wallets.return_value = mock_wallets with patch("bittensor.wallet") as mock_create_wallet: @@ -753,9 +765,10 @@ def mock_get_wallet(*args, **kwargs): stake.tao, mock_stakes[wallet.hotkey_str].tao, places=4 ) - def test_stake_with_specific_hotkeys(self): + def test_stake_with_specific_hotkeys(self, _): config = self.config config.command = "stake" + config.subcommand = "add" config.no_prompt = True config.amount = 5.0 config.wallet.name = "fake_wallet" @@ -763,7 +776,7 @@ def test_stake_with_specific_hotkeys(self): config.all_hotkeys = False # Notice no max_stake specified - mock_balance = bittensor.Balance.from_float(22.2) + mock_balance = Balance.from_float(22.2) mock_coldkey_kp = _get_mock_keypair(0, self.id()) @@ -824,9 +837,10 @@ def mock_get_wallet(*args, **kwargs): ) self.assertAlmostEqual(stake.tao, config.amount, places=4) - def test_stake_with_all_hotkeys(self): + def test_stake_with_all_hotkeys(self, _): config = self.config config.command = "stake" + config.subcommand = "add" config.no_prompt = True config.amount = 5.0 config.wallet.name = "fake_wallet" @@ -836,7 +850,7 @@ def test_stake_with_all_hotkeys(self): mock_hotkeys = ["hk0", "hk1", "hk2"] - mock_balance = bittensor.Balance.from_float(22.0) + mock_balance = Balance.from_float(22.0) mock_coldkey_kp = _get_mock_keypair(0, self.id()) @@ -880,7 +894,7 @@ def mock_get_wallet(*args, **kwargs): with patch("bittensor.wallet") as mock_create_wallet: mock_create_wallet.side_effect = mock_get_wallet with patch( - "bittensor._cli.commands.stake.get_hotkey_wallets_for_wallet" + "bittensor.commands.stake.get_hotkey_wallets_for_wallet" ) as mock_get_hotkey_wallets_for_wallet: mock_get_hotkey_wallets_for_wallet.return_value = mock_wallets @@ -921,9 +935,10 @@ def mock_get_wallet(*args, **kwargs): places=4, ) - def test_stake_with_exclude_hotkeys_from_all(self): + def test_stake_with_exclude_hotkeys_from_all(self, _): config = self.config config.command = "stake" + config.subcommand = "add" config.no_prompt = True config.amount = 5.0 config.wallet.name = "fake_wallet" @@ -933,7 +948,7 @@ def test_stake_with_exclude_hotkeys_from_all(self): mock_hotkeys = ["hk0", "hk1", "hk2"] - mock_balance = bittensor.Balance.from_float(25.0) + mock_balance = Balance.from_float(25.0) mock_coldkey_kp = _get_mock_keypair(0, self.id()) @@ -975,7 +990,7 @@ def mock_get_wallet(*args, **kwargs): return mock_wallets[0] with patch( - "bittensor._cli.commands.stake.get_hotkey_wallets_for_wallet" + "bittensor.commands.stake.get_hotkey_wallets_for_wallet" ) as mock_get_all_wallets: mock_get_all_wallets.return_value = mock_wallets with patch("bittensor.wallet") as mock_create_wallet: @@ -1022,9 +1037,10 @@ def mock_get_wallet(*args, **kwargs): balance.tao, mock_balance.tao - (config.amount * 2), places=4 ) - def test_stake_with_multiple_hotkeys_max_stake(self): + def test_stake_with_multiple_hotkeys_max_stake(self, _): config = self.config config.command = "stake" + config.subcommand = "add" config.no_prompt = True # Notie amount is not specified config.max_stake = 15.0 # The keys should have at most 15.0 tao staked after @@ -1032,12 +1048,12 @@ def test_stake_with_multiple_hotkeys_max_stake(self): config.hotkeys = ["hk0", "hk1", "hk2"] config.all_hotkeys = False - mock_balance = bittensor.Balance.from_float(config.max_stake * 3) + mock_balance = Balance.from_float(config.max_stake * 3) - mock_stakes: Dict[str, bittensor.Balance] = { - "hk0": bittensor.Balance.from_float(0.0), - "hk1": bittensor.Balance.from_float(config.max_stake * 2), - "hk2": bittensor.Balance.from_float(0.0), + mock_stakes: Dict[str, Balance] = { + "hk0": Balance.from_float(0.0), + "hk1": Balance.from_float(config.max_stake * 2), + "hk2": Balance.from_float(0.0), } mock_coldkey_kp = _get_mock_keypair(0, self.id()) @@ -1134,9 +1150,10 @@ def mock_get_wallet(*args, **kwargs): ) self.assertLessEqual(balance.tao, mock_balance.tao) - def test_stake_with_multiple_hotkeys_max_stake_not_enough_balance(self): + def test_stake_with_multiple_hotkeys_max_stake_not_enough_balance(self, _): config = self.config config.command = "stake" + config.subcommand = "add" config.no_prompt = True # Notie amount is not specified config.max_stake = 15.0 # The keys should have at most 15.0 tao staked after @@ -1144,9 +1161,7 @@ def test_stake_with_multiple_hotkeys_max_stake_not_enough_balance(self): config.hotkeys = ["hk0", "hk1", "hk2"] config.all_hotkeys = False - mock_balance = bittensor.Balance.from_float( - 15.0 * 2 - ) # Not enough for all hotkeys + mock_balance = Balance.from_float(15.0 * 2) # Not enough for all hotkeys mock_coldkey_kp = _get_mock_keypair(0, self.id()) @@ -1228,9 +1243,10 @@ def mock_get_wallet(*args, **kwargs): ) self.assertLessEqual(balance.tao, mock_balance.tao) - def test_stake_with_single_hotkey_max_stake(self): + def test_stake_with_single_hotkey_max_stake(self, _): config = self.config config.command = "stake" + config.subcommand = "add" config.no_prompt = True # Notie amount is not specified config.max_stake = 15.0 # The keys should have at most 15.0 tao staked after @@ -1238,7 +1254,7 @@ def test_stake_with_single_hotkey_max_stake(self): config.hotkeys = ["hk0"] config.all_hotkeys = False - mock_balance = bittensor.Balance.from_float(15.0 * 3) + mock_balance = Balance.from_float(15.0 * 3) mock_coldkey_kp = _get_mock_keypair(0, self.id()) @@ -1315,9 +1331,10 @@ def mock_get_wallet(*args, **kwargs): ) self.assertLessEqual(balance.tao, mock_balance.tao) - def test_stake_with_single_hotkey_max_stake_not_enough_balance(self): + def test_stake_with_single_hotkey_max_stake_not_enough_balance(self, _): config = self.config config.command = "stake" + config.subcommand = "add" config.no_prompt = True # Notie amount is not specified config.max_stake = 15.0 # The keys should have at most 15.0 tao staked after @@ -1325,7 +1342,7 @@ def test_stake_with_single_hotkey_max_stake_not_enough_balance(self): config.hotkeys = ["hk0"] config.all_hotkeys = False - mock_balance = bittensor.Balance.from_float(1.0) # Not enough balance to do max + mock_balance = Balance.from_float(1.0) # Not enough balance to do max mock_coldkey_kp = _get_mock_keypair(0, self.id()) @@ -1402,10 +1419,11 @@ def mock_get_wallet(*args, **kwargs): ) self.assertGreaterEqual(balance.tao, mock_balance.tao - config.max_stake) - def test_stake_with_single_hotkey_max_stake_enough_stake(self): + def test_stake_with_single_hotkey_max_stake_enough_stake(self, _): # tests max stake when stake >= max_stake already config = self.config config.command = "stake" + config.subcommand = "add" config.no_prompt = True # Notie amount is not specified config.max_stake = 15.0 # The keys should have at most 15.0 tao staked after @@ -1413,12 +1431,10 @@ def test_stake_with_single_hotkey_max_stake_enough_stake(self): config.hotkeys = ["hk0"] config.all_hotkeys = False - mock_balance = bittensor.Balance.from_float(config.max_stake * 3) + mock_balance = Balance.from_float(config.max_stake * 3) - mock_stakes: Dict[ - str, bittensor.Balance - ] = { # has enough stake, more than max_stake - "hk0": bittensor.Balance.from_float(config.max_stake * 2) + mock_stakes: Dict[str, Balance] = { # has enough stake, more than max_stake + "hk0": Balance.from_float(config.max_stake * 2) } mock_coldkey_kp = _get_mock_keypair(0, self.id()) @@ -1504,14 +1520,15 @@ def mock_get_wallet(*args, **kwargs): ) self.assertAlmostEqual(balance.tao, mock_balance.tao, places=4) - def test_nominate(self): + def test_nominate(self, _): config = self.config - config.command = "nominate" + config.command = "root" + config.subcommand = "nominate" config.no_prompt = True config.wallet.name = "w0" config.hotkey = "hk0" - mock_balance = bittensor.Balance.from_float(100.0) + mock_balance = Balance.from_float(100.0) mock_wallet = SimpleNamespace( name="w0", @@ -1556,20 +1573,23 @@ def mock_get_wallet(*args, **kwargs): ) self.assertTrue(is_delegate) - def test_delegate_stake(self): + def test_delegate_stake(self, _): config = self.config - config.command = "delegate" + config.command = "root" + config.subcommand = "delegate" config.no_prompt = True config.amount = 5.0 config.wallet.name = "w1" - mock_balances: Dict[str, bittensor.Balance] = { + mock_balances: Dict[str, Balance] = { # All have more than 5.0 stake - "w0": {"hk0": bittensor.Balance.from_float(10.0)}, - "w1": {"hk1": bittensor.Balance.from_float(11.1)}, + "w0": { + "hk0": Balance.from_float(10.0), + }, + "w1": {"hk1": Balance.from_float(11.1)}, } - mock_stake = bittensor.Balance.from_float(5.0) + mock_stake = Balance.from_float(5.0) mock_wallets = [] for idx, wallet_name in enumerate(list(mock_balances.keys())): @@ -1638,21 +1658,24 @@ def mock_get_wallet(*args, **kwargs): ) self.assertAlmostEqual(stake.tao, config.amount, places=4) - def test_undelegate_stake(self): + def test_undelegate_stake(self, _): config = self.config - config.command = "undelegate" + config.command = "root" + config.subcommand = "undelegate" config.no_prompt = True config.amount = 5.0 config.wallet.name = "w1" - mock_balances: Dict[str, bittensor.Balance] = { + mock_balances: Dict[str, Balance] = { # All have more than 5.0 stake - "w0": {"hk0": bittensor.Balance.from_float(10.0)}, - "w1": {"hk1": bittensor.Balance.from_float(11.1)}, + "w0": { + "hk0": Balance.from_float(10.0), + }, + "w1": {"hk1": Balance.from_float(11.1)}, } - mock_stake = bittensor.Balance.from_float(5.0) - mock_delegated = bittensor.Balance.from_float(6.0) + mock_stake = Balance.from_float(5.0) + mock_delegated = Balance.from_float(6.0) mock_wallets = [] for idx, wallet_name in enumerate(list(mock_balances.keys())): @@ -1739,16 +1762,17 @@ def mock_get_wallet(*args, **kwargs): stake.tao, mock_delegated.tao - config.amount, places=4 ) - def test_transfer(self): + def test_transfer(self, _): config = self.config - config.command = "transfer" + config.command = "wallet" + config.subcommand = "transfer" config.no_prompt = True config.amount = 3.2 config.wallet.name = "w1" - mock_balances: Dict[str, bittensor.Balance] = { - "w0": bittensor.Balance.from_float(10.0), - "w1": bittensor.Balance.from_float(config.amount + 0.001), + mock_balances: Dict[str, Balance] = { + "w0": Balance.from_float(10.0), + "w1": Balance.from_float(config.amount + 0.001), } mock_wallets = [] @@ -1806,18 +1830,17 @@ def mock_get_wallet(*args, **kwargs): balance.tao, mock_balances["w1"].tao - config.amount, places=4 ) # no fees - def test_transfer_not_enough_balance(self): + def test_transfer_not_enough_balance(self, _): config = self.config - config.command = "transfer" + config.command = "wallet" + config.subcommand = "transfer" config.no_prompt = True config.amount = 3.2 config.wallet.name = "w1" - mock_balances: Dict[str, bittensor.Balance] = { - "w0": bittensor.Balance.from_float(10.0), - "w1": bittensor.Balance.from_float( - config.amount - 0.1 - ), # not enough balance + mock_balances: Dict[str, Balance] = { + "w0": Balance.from_float(10.0), + "w1": Balance.from_float(config.amount - 0.1), # not enough balance } mock_wallets = [] @@ -1886,11 +1909,12 @@ def mock_get_wallet(*args, **kwargs): balance.tao, mock_balances["w1"].tao, places=4 ) # did not transfer - def test_register(self): + def test_register(self, _): config = self.config - config.command = "register" - config.subtensor.register.num_processes = 1 - config.subtensor.register.update_interval = 50_000 + config.command = "subnets" + config.subcommand = "register" + config.register.num_processes = 1 + config.register.update_interval = 50_000 config.no_prompt = True mock_wallet = generate_wallet(hotkey=_get_mock_keypair(100, self.id())) @@ -1900,11 +1924,9 @@ class MockException(Exception): with patch("bittensor.wallet", return_value=mock_wallet) as mock_create_wallet: with patch( - "bittensor._subtensor.extrinsics.registration.POWSolution.is_stale", + "bittensor.extrinsics.registration.POWSolution.is_stale", side_effect=MockException, ) as mock_is_stale: - mock_is_stale.return_value = False - with pytest.raises(MockException): cli = bittensor.cli(config) cli.run() @@ -1912,9 +1934,10 @@ class MockException(Exception): self.assertEqual(mock_is_stale.call_count, 1) - def test_recycle_register(self): + def test_recycle_register(self, _): config = self.config - config.command = "recycle_register" + config.command = "subnets" + config.subcommand = "recycle_register" config.no_prompt = True mock_wallet = generate_wallet(hotkey=_get_mock_keypair(100, self.id())) @@ -1922,7 +1945,7 @@ def test_recycle_register(self): # Give the wallet some balance for burning success, err = _subtensor_mock.force_set_balance( ss58_address=mock_wallet.coldkeypub.ss58_address, - balance=bittensor.Balance.from_float(200.0), + balance=Balance.from_float(200.0), ) with patch("bittensor.wallet", return_value=mock_wallet) as mock_create_wallet: @@ -1938,14 +1961,14 @@ def test_recycle_register(self): self.assertTrue(registered) - def test_stake(self): + def test_stake(self, _): amount_to_stake: Balance = Balance.from_tao(0.5) config = self.config config.no_prompt = True config.command = "stake" + config.subcommand = "add" config.amount = amount_to_stake.tao config.stake_all = False - config.wallet._mock = True config.use_password = False config.model = "core_server" config.hotkey = "hk0" @@ -1982,10 +2005,11 @@ def test_stake(self): self.assertGreater(new_stake, old_stake) - def test_metagraph(self): + def test_metagraph(self, _): config = self.config config.wallet.name = "metagraph_testwallet" - config.command = "metagraph" + config.command = "subnets" + config.subcommand = "metagraph" config.no_prompt = True # Add some neurons to the metagraph @@ -2034,28 +2058,7 @@ def register_mock_neuron(i: int) -> int: for neuron in nn: self.assertIn(str(neuron.uid), output_no_syntax) - def test_set_weights(self): - config = self.config - config.wallet.name = "set_weights_testwallet" - config.no_prompt = True - config.uids = [1, 2, 3, 4] - config.weights = [0.25, 0.25, 0.25, 0.25] - config.n_words = 12 - config.use_password = False - - config.overwrite_hotkey = True - - # First create a new hotkey - config.command = "new_hotkey" - cli = bittensor.cli(config) - cli.run() - - # Now set the weights - config.command = "set_weights" - cli.config = config - cli.run() - - def test_inspect(self): + def test_inspect(self, _): config = self.config config.wallet.name = "inspect_testwallet" config.no_prompt = True @@ -2065,43 +2068,49 @@ def test_inspect(self): config.overwrite_hotkey = True # First create a new coldkey - config.command = "new_coldkey" + config.command = "wallet" + config.subcommand = "new_coldkey" cli = bittensor.cli(config) cli.run() # Now let's give it a hotkey - config.command = "new_hotkey" + config.command = "wallet" + config.subcommand = "new_hotkey" cli.config = config cli.run() # Now inspect it - cli.config.command = "inspect" + config.command = "wallet" + cli.config.subcommand = "inspect" cli.config = config cli.run() - cli.config.command = "list" + config.command = "wallet" + cli.config.subcommand = "list" cli.config = config cli.run() +@patch("bittensor.subtensor", new_callable=return_mock_sub) class TestCLIWithNetworkUsingArgs(unittest.TestCase): """ Test the CLI by passing args directly to the bittensor.cli factory """ - def test_list_delegates(self): - cli = bittensor.cli( - args=["list_delegates", "--subtensor.network", "mock"] # Mock network - ) + def test_list_delegates(self, _): + cli = bittensor.cli(args=["root", "list_delegates"]) cli.run() - def test_list_subnets(self): + def test_list_subnets(self, _): cli = bittensor.cli( - args=["list_subnets", "--subtensor.network", "mock"] # Mock network + args=[ + "subnets", + "list", + ] ) cli.run() - def test_delegate(self): + def test_delegate(self, _): """ Test delegate add command """ @@ -2131,7 +2140,7 @@ def test_delegate(self): # Give the wallet some TAO _, err = _subtensor_mock.force_set_balance( ss58_address=mock_wallet.coldkey.ss58_address, - balance=bittensor.Balance.from_tao(20.0), + balance=Balance.from_tao(20.0), ) self.assertEqual(err, None) @@ -2154,13 +2163,12 @@ def test_delegate(self): ): # Mock wallet creation. SHOULD NOT BE REGISTERED cli = bittensor.cli( args=[ + "root", "delegate", "--subtensor.network", "mock", # Mock network "--wallet.name", "mock", - "--wallet._mock", - "True", "--delegate_ss58key", delegate_wallet.hotkey.ss58_address, "--amount", diff --git a/tests/integration_tests/test_cli_no_network.py b/tests/integration_tests/test_cli_no_network.py index 0df8e3c1c4..5aa8571211 100644 --- a/tests/integration_tests/test_cli_no_network.py +++ b/tests/integration_tests/test_cli_no_network.py @@ -19,74 +19,93 @@ import unittest from unittest.mock import MagicMock, patch -from typing import Any +from typing import Any, Optional import pytest from copy import deepcopy import re -from tests.helpers import _get_mock_coldkey +from tests.helpers import _get_mock_coldkey, __mock_wallet_factory__, MockConsole import bittensor +from bittensor import Balance +from rich.table import Table -class TestCLINoNetwork(unittest.TestCase): - _patched_subtensor = None - - @classmethod - def setUpClass(cls) -> None: - mock_delegate_info = { - "hotkey_ss58": "", - "total_stake": bittensor.Balance.from_rao(0), - "nominators": [], - "owner_ss58": "", - "take": 0.18, - "validator_permits": [], - "registrations": [], - "return_per_1000": bittensor.Balance.from_rao(0), - "total_daily_return": bittensor.Balance.from_rao(0), - } - cls._patched_subtensor = patch( - "bittensor._subtensor.subtensor_mock.MockSubtensor.__new__", - new=MagicMock( - return_value=MagicMock( - get_subnets=MagicMock(return_value=[1]), # Mock subnet 1 ONLY. - block=10_000, - get_delegates=MagicMock( - return_value=[bittensor.DelegateInfo(**mock_delegate_info)] - ), - ) +class MockException(Exception): + pass + + +mock_delegate_info = { + "hotkey_ss58": "", + "total_stake": bittensor.Balance.from_rao(0), + "nominators": [], + "owner_ss58": "", + "take": 0.18, + "validator_permits": [], + "registrations": [], + "return_per_1000": bittensor.Balance.from_rao(0), + "total_daily_return": bittensor.Balance.from_rao(0), +} + + +def return_mock_sub_1(*args, **kwargs): + return MagicMock( + return_value=MagicMock( + get_subnets=MagicMock(return_value=[1]), # Mock subnet 1 ONLY. + block=10_000, + get_delegates=MagicMock( + return_value=[bittensor.DelegateInfo(**mock_delegate_info)] ), ) - cls._patched_subtensor.start() + ) + + +def return_mock_wallet_factory(*args, **kwargs): + return MagicMock( + return_value=__mock_wallet_factory__(*args, **kwargs), + add_args=bittensor.wallet.add_args, + ) - @classmethod - def tearDownClass(cls) -> None: - cls._patched_subtensor.stop() +@patch( + "bittensor.subtensor", + new_callable=return_mock_sub_1, +) +@patch("bittensor.wallet", new_callable=return_mock_wallet_factory) +class TestCLINoNetwork(unittest.TestCase): def setUp(self): self._config = TestCLINoNetwork.construct_config() - @property def config(self): copy_ = deepcopy(self._config) return copy_ @staticmethod def construct_config(): - defaults = bittensor.Config() + parser = bittensor.cli.__create_parser__() + defaults = bittensor.config(parser=parser, args=["subnets", "metagraph"]) + + # Parse commands and subcommands + for command in bittensor.ALL_COMMANDS: + if ( + command in bittensor.ALL_COMMANDS + and "commands" in bittensor.ALL_COMMANDS[command] + ): + for subcommand in bittensor.ALL_COMMANDS[command]["commands"]: + defaults.merge( + bittensor.config(parser=parser, args=[command, subcommand]) + ) + else: + defaults.merge(bittensor.config(parser=parser, args=[command])) defaults.netuid = 1 - bittensor.subtensor.add_defaults(defaults) defaults.subtensor.network = "mock" defaults.no_version_checking = True - bittensor.axon.add_defaults(defaults) - bittensor.wallet.add_defaults(defaults) - bittensor.dataset.add_defaults(defaults) return defaults - def test_check_configs(self): - config = self.config + def test_check_configs(self, _, __): + config = self.config() config.no_prompt = True config.model = "core_server" config.dest = "no_prompt" @@ -100,13 +119,9 @@ def test_check_configs(self): config.public_key_hex = None config.proposal_hash = "" - cli = bittensor.cli - - # Get argparser - parser = cli.__create_parser__() - # Get all commands from argparser - commands = [command for command in parser._actions[1].choices] + cli_instance = bittensor.cli + # Define the response function for rich.prompt.Prompt.ask def ask_response(prompt: str) -> Any: if "delegate index" in prompt: return 0 @@ -115,16 +130,25 @@ def ask_response(prompt: str) -> Any: elif "hotkey" in prompt: return "mock" + # Patch the ask response with patch("rich.prompt.Prompt.ask", ask_response): - for cmd in commands: - config.command = cmd - cli.check_config(config) - - def test_new_coldkey(self): - config = self.config + # Loop through all commands and their subcommands + for command, command_data in bittensor.ALL_COMMANDS.items(): + config.command = command + if isinstance(command_data, dict): + for subcommand in command_data["commands"].keys(): + config.subcommand = subcommand + cli_instance.check_config(config) + else: + config.subcommand = None + cli_instance.check_config(config) + + def test_new_coldkey(self, _, __): + config = self.config() config.wallet.name = "new_coldkey_testwallet" - config.command = "new_coldkey" + config.command = "wallet" + config.subcommand = "new_coldkey" config.amount = 1 config.dest = "no_prompt" config.model = "core_server" @@ -136,10 +160,11 @@ def test_new_coldkey(self): cli = bittensor.cli(config) cli.run() - def test_new_hotkey(self): - config = self.config + def test_new_hotkey(self, _, __): + config = self.config() config.wallet.name = "new_hotkey_testwallet" - config.command = "new_hotkey" + config.command = "wallet" + config.subcommand = "new_hotkey" config.amount = 1 config.dest = "no_prompt" config.model = "core_server" @@ -151,10 +176,11 @@ def test_new_hotkey(self): cli = bittensor.cli(config) cli.run() - def test_regen_coldkey(self): - config = self.config + def test_regen_coldkey(self, _, __): + config = self.config() config.wallet.name = "regen_coldkey_testwallet" - config.command = "regen_coldkey" + config.command = "wallet" + config.subcommand = "regen_coldkey" config.amount = 1 config.dest = "no_prompt" config.model = "core_server" @@ -168,10 +194,11 @@ def test_regen_coldkey(self): cli = bittensor.cli(config) cli.run() - def test_regen_coldkeypub(self): - config = self.config + def test_regen_coldkeypub(self, _, __): + config = self.config() config.wallet.name = "regen_coldkeypub_testwallet" - config.command = "regen_coldkeypub" + config.command = "wallet" + config.subcommand = "regen_coldkeypub" config.ss58_address = "5DD26kC2kxajmwfbbZmVmxhrY9VeeyR1Gpzy9i8wxLUg6zxm" config.public_key = None config.use_password = False @@ -181,10 +208,11 @@ def test_regen_coldkeypub(self): cli = bittensor.cli(config) cli.run() - def test_regen_hotkey(self): - config = self.config + def test_regen_hotkey(self, _, __): + config = self.config() config.wallet.name = "regen_hotkey_testwallet" - config.command = "regen_hotkey" + config.command = "wallet" + config.subcommand = "regen_hotkey" config.amount = 1 config.model = "core_server" config.mnemonic = "faculty decade seven jelly gospel axis next radio grain radio remain gentle" @@ -197,7 +225,7 @@ def test_regen_hotkey(self): cli = bittensor.cli(config) cli.run() - def test_list(self): + def test_list(self, _, __): # Mock IO for wallet with patch( "bittensor.wallet", @@ -230,11 +258,12 @@ def test_list(self): ), ], ): - config = self.config + config = self.config() config.wallet.path = "tmp/walletpath" config.wallet.name = "mock_wallet" config.no_prompt = True - config.command = "list" + config.command = "wallet" + config.subcommand = "list" cli = bittensor.cli(config) with patch( @@ -250,27 +279,28 @@ def test_list(self): ): cli.run() - def test_list_no_wallet(self): - # Mock IO for wallet + def test_list_no_wallet(self, _, __): with patch( - "bittensor.Wallet.coldkeypub_file", - MagicMock( - exists_on_device=MagicMock(return_value=False) # Wallet doesn't exist - ), + "bittensor.wallet", + side_effect=[ + MagicMock( + coldkeypub_file=MagicMock( + exists_on_device=MagicMock(return_value=True) + ) + ) + ], ): - config = self.config + config = self.config() config.wallet.path = "/tmp/test_cli_test_list_no_wallet" config.no_prompt = True - config.command = "list" + config.command = "wallet" + config.subcommand = "list" cli = bittensor.cli(config) # This shouldn't raise an error anymore cli.run() - def test_btcli_help(self): - """ - Verify the correct help text is output when the --help flag is passed - """ + def test_btcli_help(self, _, __): with pytest.raises(SystemExit) as pytest_wrapped_e: with patch( "argparse.ArgumentParser._print_message", return_value=None @@ -278,43 +308,36 @@ def test_btcli_help(self): args = ["--help"] bittensor.cli(args=args).run() - # Should try to print help mock_print_message.assert_called_once() call_args = mock_print_message.call_args - args, _ = call_args - help_out = args[0] - - # Expected help output even if parser isn't working well - ## py3.6-3.9 or py3.10+ - assert "optional arguments" in help_out or "options" in help_out - # Expected help output if all commands are listed - assert "positional arguments" in help_out - # Verify that cli is printing the help message for - # Get argparser + help_out = call_args[0][0] + + # Extract commands from the help text. + commands_section = re.search( + r"positional arguments:.*?{(.+?)}", help_out, re.DOTALL + ).group(1) + extracted_commands = [cmd.strip() for cmd in commands_section.split(",")] + + # Get expected commands parser = bittensor.cli.__create_parser__() - # Get all commands from argparser - commands = [command for command in parser._actions[1].choices] - # Verify that all commands are listed in the help message, AND - # Verify there are no duplicate commands - ## Listed twice. Once in the positional arguments and once in the optional arguments - for command in commands: - pat = re.compile(rf"\n\s+({command})[^\S\r\n]+\w") - matches = pat.findall(help_out) - self.assertGreaterEqual( - len(matches), 1, f"Command {command} not found in help output" - ) - self.assertLess( - len(matches), 2, f"Duplicate command {command} in help output" - ) + expected_commands = [command for command in parser._actions[1].choices] - def test_register_cuda_use_cuda_flag(self): - class ExitEarlyException(Exception): - """Raised by mocked function to exit early""" + # Validate each expected command is in extracted commands + for command in expected_commands: + assert ( + command in extracted_commands + ), f"Command {command} not found in help output" - pass + # Check for duplicates + assert len(extracted_commands) == len( + set(extracted_commands) + ), "Duplicate commands found in help output" + @patch("torch.cuda.is_available", return_value=True) + def test_register_cuda_use_cuda_flag(self, _, __, patched_sub): base_args = [ + "subnets", "register", "--wallet.path", "tmp/walletpath", @@ -325,124 +348,147 @@ class ExitEarlyException(Exception): "--no_prompt", "--cuda.dev_id", "0", - "--network", - "mock", ] - bittensor.subtensor.check_config = MagicMock(return_value=True) - with patch("torch.cuda.is_available", return_value=True): - with patch("bittensor.Subtensor.get_subnets", return_value=[1]): - with patch( - "bittensor.Subtensor.subnet_exists", - side_effect=lambda netuid: netuid == 1, - ): - with patch( - "bittensor.Subtensor.register", side_effect=ExitEarlyException - ): - # Should be able to set true without argument - args = base_args + [ - "--subtensor.register.cuda.use_cuda", # should be True without any arugment - ] - with pytest.raises(ExitEarlyException): - cli = bittensor.cli(args=args) - cli.run() - assert ( - cli.config.subtensor.register.cuda.get("use_cuda") == True - ) # should be None + patched_sub.return_value = MagicMock( + get_subnets=MagicMock(return_value=[1]), + subnet_exists=MagicMock(return_value=True), + register=MagicMock(side_effect=MockException), + ) - # Should be able to set to false with no argument - args = base_args + [ - "--subtensor.register.cuda.no_cuda", - ] - with pytest.raises(ExitEarlyException): - cli = bittensor.cli(args=args) - cli.run() + # Should be able to set true without argument + args = base_args + [ + "--register.cuda.use_cuda", # should be True without any arugment + ] + with pytest.raises(MockException): + cli = bittensor.cli(args=args) + cli.run() - assert cli.config.subtensor.register.cuda.use_cuda == False + self.assertEqual(cli.config.register.cuda.get("use_cuda"), True) + # Should be able to set to false with no argument -class MockException(Exception): - pass + args = base_args + [ + "--register.cuda.no_cuda", + ] + with pytest.raises(MockException): + cli = bittensor.cli(args=args) + cli.run() + + self.assertEqual(cli.config.register.cuda.get("use_cuda"), False) +def return_mock_sub_2(*args, **kwargs): + return MagicMock( + return_value=MagicMock( + get_subnet_burn_cost=MagicMock(return_value=0.1), + get_subnets=MagicMock(return_value=[1]), # Need to pass check config + get_delegates=MagicMock( + return_value=[ + bittensor.DelegateInfo( + hotkey_ss58="", + total_stake=Balance.from_rao(0), + nominators=[], + owner_ss58="", + take=0.18, + validator_permits=[], + registrations=[], + return_per_1000=Balance(0.0), + total_daily_return=Balance(0.0), + ) + ] + ), + block=10_000, + ), + add_args=bittensor.subtensor.add_args, + ) + + +@patch("bittensor.wallet", new_callable=return_mock_wallet_factory) +@patch("bittensor.subtensor", new_callable=return_mock_sub_2) class TestEmptyArgs(unittest.TestCase): """ Test that the CLI doesn't crash when no args are passed """ - _patched_subtensor = None - - @classmethod - def setUpClass(cls) -> None: - cls._patched_subtensor = patch( - "bittensor._subtensor.subtensor_mock.MockSubtensor.__new__", new=MagicMock() - ) - cls._patched_subtensor.start() - - @classmethod - def tearDownClass(cls) -> None: - cls._patched_subtensor.stop() - @patch("rich.prompt.PromptBase.ask", side_effect=MockException) - def test_command_no_args(self, patched_prompt_ask): + def test_command_no_args(self, _, __, patched_prompt_ask): # Get argparser parser = bittensor.cli.__create_parser__() # Get all commands from argparser - commands = [command for command in parser._actions[1].choices] - - # Test that each command can be run with no args + commands = [ + command + for command in parser._actions[1].choices + if len(command) > 1 # Skip singleton aliases + and command + not in [ + "subnet", + "sudos", + "stakes", + "roots", + "wallets", + "st", + "su", + ] # Skip duplicate aliases + ] + # Test that each command and its subcommands can be run with no args for command in commands: - try: - bittensor.cli(args=[command]).run() - except MockException: - pass # Expected exception + command_data = bittensor.ALL_COMMANDS.get(command) + + # If command is dictionary, it means it has subcommands + if isinstance(command_data, dict): + for subcommand in command_data["commands"].keys(): + try: + # Run each subcommand + bittensor.cli(args=[command, subcommand]).run() + except MockException: + pass # Expected exception + else: + try: + # If no subcommands, just run the command + bittensor.cli(args=[command]).run() + except MockException: + pass # Expected exception # Should not raise any other exceptions -class TestCLIDefaultsNoNetwork(unittest.TestCase): - _patched_subtensor = None - - @classmethod - def setUpClass(cls) -> None: - mock_delegate_info = { - "hotkey_ss58": "", - "total_stake": bittensor.Balance.from_rao(0), - "nominators": [], - "owner_ss58": "", - "take": 0.18, - "validator_permits": [], - "registrations": [], - "return_per_1000": bittensor.Balance.from_rao(0), - "total_daily_return": bittensor.Balance.from_rao(0), - } - cls._patched_subtensor = patch( - "bittensor._subtensor.subtensor_mock.MockSubtensor.__new__", - new=MagicMock( - return_value=MagicMock( - get_subnets=MagicMock(return_value=[1]), # Mock subnet 1 ONLY. - block=10_000, - get_delegates=MagicMock( - return_value=[bittensor.DelegateInfo(**mock_delegate_info)] - ), - ) +mock_delegate_info = { + "hotkey_ss58": "", + "total_stake": bittensor.Balance.from_rao(0), + "nominators": [], + "owner_ss58": "", + "take": 0.18, + "validator_permits": [], + "registrations": [], + "return_per_1000": bittensor.Balance.from_rao(0), + "total_daily_return": bittensor.Balance.from_rao(0), +} + + +def return_mock_sub_3(*args, **kwargs): + return MagicMock( + return_value=MagicMock( + get_subnets=MagicMock(return_value=[1]), # Mock subnet 1 ONLY. + block=10_000, + get_delegates=MagicMock( + return_value=[bittensor.DelegateInfo(**mock_delegate_info)] ), - ) - cls._patched_subtensor.start() + ), + block=10_000, + ) - @classmethod - def tearDownClass(cls) -> None: - cls._patched_subtensor.stop() - def test_inspect_prompt_wallet_name(self): +@patch("bittensor.subtensor", new_callable=return_mock_sub_3) +class TestCLIDefaultsNoNetwork(unittest.TestCase): + def test_inspect_prompt_wallet_name(self, _): # Patch command to exit early - with patch( - "bittensor._cli.commands.inspect.InspectCommand.run", return_value=None - ): + with patch("bittensor.commands.inspect.InspectCommand.run", return_value=None): # Test prompt happens when no wallet name is passed with patch("rich.prompt.Prompt.ask") as mock_ask_prompt: cli = bittensor.cli( args=[ + "wallet", "inspect", # '--wallet.name', 'mock', ] @@ -456,6 +502,7 @@ def test_inspect_prompt_wallet_name(self): with patch("rich.prompt.Prompt.ask") as mock_ask_prompt: cli = bittensor.cli( args=[ + "wallet", "inspect", "--wallet.name", "coolwalletname", @@ -470,6 +517,7 @@ def test_inspect_prompt_wallet_name(self): with patch("rich.prompt.Prompt.ask") as mock_ask_prompt: cli = bittensor.cli( args=[ + "wallet", "inspect", "--wallet.name", "default", @@ -480,15 +528,16 @@ def test_inspect_prompt_wallet_name(self): # NO prompt happened mock_ask_prompt.assert_not_called() - def test_overview_prompt_wallet_name(self): + def test_overview_prompt_wallet_name(self, _): # Patch command to exit early with patch( - "bittensor._cli.commands.overview.OverviewCommand.run", return_value=None + "bittensor.commands.overview.OverviewCommand.run", return_value=None ): # Test prompt happens when no wallet name is passed with patch("rich.prompt.Prompt.ask") as mock_ask_prompt: cli = bittensor.cli( args=[ + "wallet", "overview", # '--wallet.name', 'mock', "--netuid", @@ -504,6 +553,7 @@ def test_overview_prompt_wallet_name(self): with patch("rich.prompt.Prompt.ask") as mock_ask_prompt: cli = bittensor.cli( args=[ + "wallet", "overview", "--wallet.name", "coolwalletname", @@ -520,6 +570,7 @@ def test_overview_prompt_wallet_name(self): with patch("rich.prompt.Prompt.ask") as mock_ask_prompt: cli = bittensor.cli( args=[ + "wallet", "overview", "--wallet.name", "default", @@ -532,13 +583,14 @@ def test_overview_prompt_wallet_name(self): # NO prompt happened mock_ask_prompt.assert_not_called() - def test_stake_prompt_wallet_name_and_hotkey_name(self): + def test_stake_prompt_wallet_name_and_hotkey_name(self, _): base_args = [ "stake", + "add", "--all", ] # Patch command to exit early - with patch("bittensor._cli.commands.stake.StakeCommand.run", return_value=None): + with patch("bittensor.commands.stake.StakeCommand.run", return_value=None): # Test prompt happens when # - wallet name IS NOT passed, AND # - hotkey name IS NOT passed @@ -692,15 +744,14 @@ def test_stake_prompt_wallet_name_and_hotkey_name(self): # NO prompt happened mock_ask_prompt.assert_not_called() - def test_unstake_prompt_wallet_name_and_hotkey_name(self): + def test_unstake_prompt_wallet_name_and_hotkey_name(self, _): base_args = [ - "unstake", + "stake", + "remove", "--all", ] # Patch command to exit early - with patch( - "bittensor._cli.commands.unstake.UnStakeCommand.run", return_value=None - ): + with patch("bittensor.commands.unstake.UnStakeCommand.run", return_value=None): # Test prompt happens when # - wallet name IS NOT passed, AND # - hotkey name IS NOT passed @@ -853,12 +904,17 @@ def test_unstake_prompt_wallet_name_and_hotkey_name(self): # NO prompt happened mock_ask_prompt.assert_not_called() - def test_delegate_prompt_wallet_name(self): - base_args = ["delegate", "--all", "--delegate_ss58key", _get_mock_coldkey(0)] + def test_delegate_prompt_wallet_name(self, _): + base_args = [ + "root", + "delegate", + "--all", + "--delegate_ss58key", + _get_mock_coldkey(0), + ] # Patch command to exit early with patch( - "bittensor._cli.commands.delegates.DelegateStakeCommand.run", - return_value=None, + "bittensor.commands.delegates.DelegateStakeCommand.run", return_value=None ): # Test prompt happens when # - wallet name IS NOT passed @@ -909,12 +965,17 @@ def test_delegate_prompt_wallet_name(self): # NO prompt happened mock_ask_prompt.assert_not_called() - def test_undelegate_prompt_wallet_name(self): - base_args = ["undelegate", "--all", "--delegate_ss58key", _get_mock_coldkey(0)] + def test_undelegate_prompt_wallet_name(self, _): + base_args = [ + "root", + "undelegate", + "--all", + "--delegate_ss58key", + _get_mock_coldkey(0), + ] # Patch command to exit early with patch( - "bittensor._cli.commands.delegates.DelegateUnstakeCommand.run", - return_value=None, + "bittensor.commands.delegates.DelegateUnstakeCommand.run", return_value=None ): # Test prompt happens when # - wallet name IS NOT passed @@ -965,11 +1026,12 @@ def test_undelegate_prompt_wallet_name(self): # NO prompt happened mock_ask_prompt.assert_not_called() - def test_delegate_prompt_hotkey(self): + def test_delegate_prompt_hotkey(self, _): # Tests when # - wallet name IS passed, AND # - delegate hotkey IS NOT passed base_args = [ + "root", "delegate", "--all", "--wallet.name", @@ -977,9 +1039,9 @@ def test_delegate_prompt_hotkey(self): ] delegate_ss58 = _get_mock_coldkey(0) - with patch("bittensor._cli.commands.delegates.show_delegates"): + with patch("bittensor.commands.delegates.show_delegates"): with patch( - "bittensor.Subtensor.get_delegates", + "bittensor.subtensor.subtensor.get_delegates", return_value=[ bittensor.DelegateInfo( hotkey_ss58=delegate_ss58, # return delegate with mock coldkey @@ -996,7 +1058,7 @@ def test_delegate_prompt_hotkey(self): ): # Patch command to exit early with patch( - "bittensor._cli.commands.delegates.DelegateStakeCommand.run", + "bittensor.commands.delegates.DelegateStakeCommand.run", return_value=None, ): # Test prompt happens when @@ -1051,11 +1113,12 @@ def test_delegate_prompt_hotkey(self): # NO prompt happened mock_ask_prompt.assert_not_called() - def test_undelegate_prompt_hotkey(self): + def test_undelegate_prompt_hotkey(self, _): # Tests when # - wallet name IS passed, AND # - delegate hotkey IS NOT passed base_args = [ + "root", "undelegate", "--all", "--wallet.name", @@ -1063,9 +1126,9 @@ def test_undelegate_prompt_hotkey(self): ] delegate_ss58 = _get_mock_coldkey(0) - with patch("bittensor._cli.commands.delegates.show_delegates"): + with patch("bittensor.commands.delegates.show_delegates"): with patch( - "bittensor.Subtensor.get_delegates", + "bittensor.subtensor.subtensor.get_delegates", return_value=[ bittensor.DelegateInfo( hotkey_ss58=delegate_ss58, # return delegate with mock coldkey @@ -1082,7 +1145,7 @@ def test_undelegate_prompt_hotkey(self): ): # Patch command to exit early with patch( - "bittensor._cli.commands.delegates.DelegateUnstakeCommand.run", + "bittensor.commands.delegates.DelegateUnstakeCommand.run", return_value=None, ): # Test prompt happens when @@ -1137,6 +1200,79 @@ def test_undelegate_prompt_hotkey(self): # NO prompt happened mock_ask_prompt.assert_not_called() + def test_vote_command_prompt_proposal_hash(self, _): + """Test that the vote command prompts for proposal_hash when it is not passed""" + base_args = [ + "root", + "senate_vote", + "--wallet.name", + "mock", + "--wallet.hotkey", + "mock_hotkey", + ] + + mock_proposal_hash = "mock_proposal_hash" + + with patch("bittensor.subtensor.subtensor.is_senate_member", return_value=True): + with patch( + "bittensor.subtensor.subtensor.get_vote_data", + return_value={"index": 1}, + ): + # Patch command to exit early + with patch( + "bittensor.commands.senate.VoteCommand.run", + return_value=None, + ): + # Test prompt happens when + # - proposal_hash IS NOT passed + with patch("rich.prompt.Prompt.ask") as mock_ask_prompt: + mock_ask_prompt.side_effect = [ + mock_proposal_hash # Proposal hash + ] + + cli = bittensor.cli( + args=base_args + # proposal_hash not added + ) + cli.run() + + # Prompt happened + mock_ask_prompt.assert_called() + self.assertEqual( + mock_ask_prompt.call_count, + 1, + msg="Prompt should have been called once", + ) + args0, kwargs0 = mock_ask_prompt.call_args_list[0] + combined_args_kwargs0 = [arg for arg in args0] + [ + val for val in kwargs0.values() + ] + # check that prompt was called for proposal_hash + self.assertTrue( + any( + filter( + lambda x: "proposal" in x.lower(), + combined_args_kwargs0, + ) + ), + msg=f"Prompt should have been called for proposal: {combined_args_kwargs0}", + ) + + # Test NO prompt happens when + # - proposal_hash IS passed + with patch("rich.prompt.Prompt.ask") as mock_ask_prompt: + cli = bittensor.cli( + args=base_args + + [ + "--proposal_hash", + mock_proposal_hash, + ] + ) + cli.run() + + # NO prompt happened + mock_ask_prompt.assert_not_called() + if __name__ == "__main__": unittest.main() diff --git a/tests/integration_tests/test_ipfs.py b/tests/integration_tests/test_ipfs.py deleted file mode 100644 index 153635b801..0000000000 --- a/tests/integration_tests/test_ipfs.py +++ /dev/null @@ -1,31 +0,0 @@ -import bittensor -import json - - -def test_ipfs_init(): - ipfs = bittensor.Ipfs() - - assert ipfs.cat == "http://global.ipfs.opentensor.ai/api/v0/cat" - assert ipfs.node_get == "http://global.ipfs.opentensor.ai/api/v0/object/get" - assert ipfs.ipns_resolve == "http://global.ipfs.opentensor.ai/api/v0/name/resolve" - - assert ipfs.mountain_hash == "QmSdDg6V9dgpdAFtActs75Qfc36qJtm9y8a7yrQ1rHm7ZX" - assert ( - ipfs.latest_neurons_ipns - == "k51qzi5uqu5di1eoe0o91g32tbfsgikva6mvz0jw0414zhxzhiakana67shoh7" - ) - assert ( - ipfs.historical_neurons_ipns - == "k51qzi5uqu5dhf5yxm3kqw9hyrv28q492p3t32s23059z911a23l30ai6ziceh" - ) - - assert ipfs.refresh_corpus == False - - -def test_retrieve_directory(): - ipfs = bittensor.Ipfs() - - directory = ipfs.retrieve_directory(ipfs.node_get, (("arg", ipfs.mountain_hash),)) - folder_list = json.loads(directory.text) - assert directory.status_code == 200 - assert len(folder_list) > 1 diff --git a/tests/integration_tests/test_metagraph_integration.py b/tests/integration_tests/test_metagraph_integration.py index d7cfd32ba8..47ab8abe55 100644 --- a/tests/integration_tests/test_metagraph_integration.py +++ b/tests/integration_tests/test_metagraph_integration.py @@ -17,11 +17,11 @@ # DEALINGS IN THE SOFTWARE. import bittensor +from bittensor.mock import MockSubtensor import torch import pytest -from bittensor._subtensor.subtensor_mock import MockSubtensor -_subtensor_mock: MockSubtensor = bittensor.subtensor(network="mock", _mock=True) +_subtensor_mock: MockSubtensor = MockSubtensor() def setUpModule(): @@ -35,23 +35,23 @@ def setUpModule(): class TestMetagraph: def setup_method(self): - self.sub = bittensor.subtensor(_mock=True) - self.metagraph = bittensor.metagraph(netuid=3, network="mock") + self.sub = MockSubtensor() + self.metagraph = bittensor.metagraph(netuid=3, network="mock", sync=False) def test_print_empty(self): print(self.metagraph) def test_lite_sync(self): - self.metagraph.sync(lite=True) + self.metagraph.sync(lite=True, subtensor=self.sub) def test_full_sync(self): - self.metagraph.sync(lite=False) + self.metagraph.sync(lite=False, subtensor=self.sub) def test_sync_block_0(self): - self.metagraph.sync(lite=True, block=0) + self.metagraph.sync(lite=True, block=0, subtensor=self.sub) def test_load_sync_save(self): - self.metagraph.sync(lite=True) + self.metagraph.sync(lite=True, subtensor=self.sub) self.metagraph.save() self.metagraph.load() self.metagraph.save() diff --git a/tests/integration_tests/test_prometheus.py b/tests/integration_tests/test_prometheus.py deleted file mode 100644 index 70a847461d..0000000000 --- a/tests/integration_tests/test_prometheus.py +++ /dev/null @@ -1,44 +0,0 @@ -import bittensor - -import pytest -import unittest -from unittest.mock import MagicMock, patch -from bittensor._subtensor.subtensor_mock import MockSubtensor -from tests.helpers import _get_mock_wallet - -_subtensor_mock: MockSubtensor = bittensor.subtensor(network="mock", _mock=True) - - -def setUpModule(): - _subtensor_mock.reset() - - _subtensor_mock.create_subnet(netuid=3) - - _subtensor_mock.set_difficulty(netuid=3, difficulty=0) - - -class TestPrometheus(unittest.TestCase): - def setUp(self): - self.subtensor = bittensor.subtensor(network="mock") - self.wallet = _get_mock_wallet() - - def test_init_prometheus_success(self): - with patch.object( - self.subtensor, "_do_serve_prometheus", return_value=(True, None) - ): - with patch("prometheus_client.start_http_server"): - self.assertTrue( - bittensor.prometheus( - wallet=self.wallet, subtensor=self.subtensor, netuid=3 - ) - ) - - def test_init_prometheus_failed(self): - with patch.object( - self.subtensor, "_do_serve_prometheus", return_value=(False, "Mock failure") - ): - with patch("prometheus_client.start_http_server"): - with pytest.raises(Exception): - bittensor.prometheus( - wallet=self.wallet, subtensor=self.subtensor, netuid=3 - ) diff --git a/tests/integration_tests/test_subtensor_integration.py b/tests/integration_tests/test_subtensor_integration.py index ed3dad3531..8448f9098b 100644 --- a/tests/integration_tests/test_subtensor_integration.py +++ b/tests/integration_tests/test_subtensor_integration.py @@ -24,10 +24,10 @@ from types import SimpleNamespace import bittensor +from bittensor.mock import MockSubtensor import pytest from bittensor.utils.balance import Balance from substrateinterface import Keypair -from bittensor._subtensor.subtensor_mock import MockSubtensor from tests.helpers import ( _get_mock_hotkey, _get_mock_coldkey, @@ -49,7 +49,7 @@ def setUp(self): ) self.balance = Balance.from_tao(1000) self.mock_neuron = MagicMock() # NOTE: this might need more sophistication - self.subtensor = bittensor.subtensor(network="mock") # own instance per test + self.subtensor = MockSubtensor() # own instance per test @classmethod def setUpClass(cls) -> None: @@ -59,7 +59,7 @@ def setUpClass(cls) -> None: cls._mock_console_patcher.start() # Keeps the same mock network for all tests. This stops the network from being re-setup for each test. - cls._mock_subtensor = bittensor.subtensor(network="mock") + cls._mock_subtensor = MockSubtensor() cls._do_setup_subnet() @@ -89,16 +89,7 @@ def test_network_overrides(self): # Mock network calls with patch("substrateinterface.SubstrateInterface.connect_websocket"): with patch("substrateinterface.SubstrateInterface.reload_type_registry"): - # Choose arg over config - sub0 = bittensor.subtensor( - config=config0, chain_endpoint="wss://fin.subtensor.io" - ) - self.assertEqual( - sub0.chain_endpoint, - "wss://fin.subtensor.io", - msg="Explicit chain_endpoint arg should override config.chain_endpoint", - ) - + print(bittensor.subtensor, type(bittensor.subtensor)) # Choose network arg over config sub1 = bittensor.subtensor(config=config1, network="local") self.assertEqual( @@ -107,12 +98,12 @@ def test_network_overrides(self): msg="Explicit network arg should override config.network", ) - # Choose chain_endpoint config over network config + # Choose network config over chain_endpoint config sub2 = bittensor.subtensor(config=config0) - self.assertEqual( + self.assertNotEqual( sub2.chain_endpoint, - config0.subtensor.chain_endpoint, - msg="config.chain_endpoint should override choice derived from config.network", + bittensor.__finney_entrypoint__, # Here we expect the endpoint corresponding to the network "finney" + msg="config.network should override config.chain_endpoint", ) sub3 = bittensor.subtensor(config=config1) @@ -137,12 +128,11 @@ def test_unstake(self): self.subtensor.get_neuron_for_pubkey_and_subnet = MagicMock( return_value=self.mock_neuron ) - with patch( - "bittensor.Subtensor.get_stake_for_coldkey_and_hotkey", - return_value=Balance.from_tao(500), - ): - success = self.subtensor.unstake(self.wallet, amount=200) - self.assertTrue(success, msg="Unstake should succeed") + self.subtensor.get_stake_for_coldkey_and_hotkey = MagicMock( + return_value=Balance.from_tao(500) + ) + success = self.subtensor.unstake(self.wallet, amount=200) + self.assertTrue(success, msg="Unstake should succeed") def test_unstake_inclusion(self): self.subtensor._do_unstake = MagicMock(return_value=True) @@ -153,18 +143,16 @@ def test_unstake_inclusion(self): self.subtensor.register = MagicMock(return_value=True) self.subtensor.get_balance = MagicMock(return_value=self.balance) - self.subtensor.get_neuron_for_pubkey_and_subnet = MagicMock( return_value=self.mock_neuron ) - with patch( - "bittensor.Subtensor.get_stake_for_coldkey_and_hotkey", - return_value=Balance.from_tao(500), - ): - success = self.subtensor.unstake( - self.wallet, amount=200, wait_for_inclusion=True - ) - self.assertTrue(success, msg="Unstake should succeed") + self.subtensor.get_stake_for_coldkey_and_hotkey = MagicMock( + return_value=Balance.from_tao(500) + ) + success = self.subtensor.unstake( + self.wallet, amount=200, wait_for_inclusion=True + ) + self.assertTrue(success, msg="Unstake should succeed") def test_unstake_failed(self): self.subtensor._do_unstake = MagicMock(return_value=False) @@ -175,14 +163,11 @@ def test_unstake_failed(self): self.subtensor.get_neuron_for_pubkey_and_subnet = MagicMock( return_value=self.mock_neuron ) - with patch( - "bittensor.Subtensor.get_stake_for_coldkey_and_hotkey", - return_value=Balance.from_tao(500), - ): - fail = self.subtensor.unstake( - self.wallet, amount=200, wait_for_inclusion=True - ) - self.assertFalse(fail, msg="Unstake should fail") + self.subtensor.get_stake_for_coldkey_and_hotkey = MagicMock( + return_value=Balance.from_tao(500) + ) + fail = self.subtensor.unstake(self.wallet, amount=200, wait_for_inclusion=True) + self.assertFalse(fail, msg="Unstake should fail") def test_stake(self): self.subtensor._do_stake = MagicMock(return_value=True) @@ -197,16 +182,14 @@ def test_stake(self): self.subtensor.get_neuron_for_pubkey_and_subnet = MagicMock( return_value=self.mock_neuron ) - with patch( - "bittensor.Subtensor.get_stake_for_coldkey_and_hotkey", - return_value=Balance.from_tao(500), - ): - with patch( - "bittensor.Subtensor.get_hotkey_owner", - return_value=self.wallet.coldkeypub.ss58_address, - ): - success = self.subtensor.add_stake(self.wallet, amount=200) - self.assertTrue(success, msg="Stake should succeed") + self.subtensor.get_stake_for_coldkey_and_hotkey = MagicMock( + return_value=Balance.from_tao(500) + ) + self.subtensor.get_hotkey_owner = MagicMock( + return_value=self.wallet.coldkeypub.ss58_address + ) + success = self.subtensor.add_stake(self.wallet, amount=200) + self.assertTrue(success, msg="Stake should succeed") def test_stake_inclusion(self): self.subtensor._do_stake = MagicMock(return_value=True) @@ -221,18 +204,16 @@ def test_stake_inclusion(self): self.subtensor.get_neuron_for_pubkey_and_subnet = MagicMock( return_value=self.mock_neuron ) - with patch( - "bittensor.Subtensor.get_stake_for_coldkey_and_hotkey", - return_value=Balance.from_tao(500), - ): - with patch( - "bittensor.Subtensor.get_hotkey_owner", - return_value=self.wallet.coldkeypub.ss58_address, - ): - success = self.subtensor.add_stake( - self.wallet, amount=200, wait_for_inclusion=True - ) - self.assertTrue(success, msg="Stake should succeed") + self.subtensor.get_stake_for_coldkey_and_hotkey = MagicMock( + return_value=Balance.from_tao(500) + ) + self.subtensor.get_hotkey_owner = MagicMock( + return_value=self.wallet.coldkeypub.ss58_address + ) + success = self.subtensor.add_stake( + self.wallet, amount=200, wait_for_inclusion=True + ) + self.assertTrue(success, msg="Stake should succeed") def test_stake_failed(self): self.subtensor._do_stake = MagicMock(return_value=False) @@ -247,18 +228,16 @@ def test_stake_failed(self): self.subtensor.get_neuron_for_pubkey_and_subnet = MagicMock( return_value=self.mock_neuron ) - with patch( - "bittensor.Subtensor.get_stake_for_coldkey_and_hotkey", - return_value=Balance.from_tao(500), - ): - with patch( - "bittensor.Subtensor.get_hotkey_owner", - return_value=self.wallet.coldkeypub.ss58_address, - ): - fail = self.subtensor.add_stake( - self.wallet, amount=200, wait_for_inclusion=True - ) - self.assertFalse(fail, msg="Stake should fail") + self.subtensor.get_stake_for_coldkey_and_hotkey = MagicMock( + return_value=Balance.from_tao(500) + ) + self.subtensor.get_hotkey_owner = MagicMock( + return_value=self.wallet.coldkeypub.ss58_address + ) + fail = self.subtensor.add_stake( + self.wallet, amount=200, wait_for_inclusion=True + ) + self.assertFalse(fail, msg="Stake should fail") def test_transfer(self): fake_coldkey = _get_mock_coldkey(1) @@ -452,54 +431,48 @@ def test_registration_multiprocessed_already_registered(self): mock_neuron = MagicMock() mock_neuron.is_null = True - with patch("bittensor.Subtensor.difficulty"): - # patch solution queue to return None + # patch solution queue to return None + with patch( + "multiprocessing.queues.Queue.get", return_value=None + ) as mock_queue_get: + # patch time queue get to raise Empty exception with patch( - "multiprocessing.queues.Queue.get", return_value=None - ) as mock_queue_get: - # patch time queue get to raise Empty exception - with patch( - "multiprocessing.queues.Queue.get_nowait", side_effect=QueueEmpty - ) as mock_queue_get_nowait: - wallet = _get_mock_wallet( - hotkey=_get_mock_keypair(0, self.id()), - coldkey=_get_mock_keypair(1, self.id()), - ) - self.subtensor.is_hotkey_registered = MagicMock( - side_effect=is_registered_return_values - ) + "multiprocessing.queues.Queue.get_nowait", side_effect=QueueEmpty + ) as mock_queue_get_nowait: + wallet = _get_mock_wallet( + hotkey=_get_mock_keypair(0, self.id()), + coldkey=_get_mock_keypair(1, self.id()), + ) + self.subtensor.is_hotkey_registered = MagicMock( + side_effect=is_registered_return_values + ) - self.subtensor.difficulty = MagicMock(return_value=1) - self.subtensor.get_neuron_for_pubkey_and_subnet = MagicMock( - side_effect=mock_neuron - ) - self.subtensor._do_pow_register = MagicMock( - return_value=(True, None) - ) + self.subtensor.difficulty = MagicMock(return_value=1) + self.subtensor.get_neuron_for_pubkey_and_subnet = MagicMock( + side_effect=mock_neuron + ) + self.subtensor._do_pow_register = MagicMock(return_value=(True, None)) - with patch("bittensor.__console__.status") as mock_set_status: - # Need to patch the console status to avoid opening a parallel live display - mock_set_status.__enter__ = MagicMock(return_value=True) - mock_set_status.__exit__ = MagicMock(return_value=True) - - # should return True - assert ( - self.subtensor.register( - wallet=wallet, - netuid=3, - num_processes=3, - update_interval=5, - ) - == True - ) + with patch("bittensor.__console__.status") as mock_set_status: + # Need to patch the console status to avoid opening a parallel live display + mock_set_status.__enter__ = MagicMock(return_value=True) + mock_set_status.__exit__ = MagicMock(return_value=True) - # calls until True and once again before exiting subtensor class - # This assertion is currently broken when difficulty is too low + # should return True assert ( - self.subtensor.is_hotkey_registered.call_count - == workblocks_before_is_registered + 2 + self.subtensor.register( + wallet=wallet, netuid=3, num_processes=3, update_interval=5 + ) + == True ) + # calls until True and once again before exiting subtensor class + # This assertion is currently broken when difficulty is too low + assert ( + self.subtensor.is_hotkey_registered.call_count + == workblocks_before_is_registered + 2 + ) + def test_registration_partly_failed(self): do_pow_register_mock = MagicMock( side_effect=[(False, "Failed"), (False, "Failed"), (True, None)] @@ -511,31 +484,29 @@ def is_registered_side_effect(*args, **kwargs): current_block = [i for i in range(0, 100)] - with patch( - "bittensor.Subtensor.get_neuron_for_pubkey_and_subnet", - return_value=bittensor.NeuronInfo._null_neuron(), - ): - with patch("bittensor.Subtensor.difficulty"): - wallet = _get_mock_wallet( - hotkey=_get_mock_keypair(0, self.id()), - coldkey=_get_mock_keypair(1, self.id()), - ) + wallet = _get_mock_wallet( + hotkey=_get_mock_keypair(0, self.id()), + coldkey=_get_mock_keypair(1, self.id()), + ) - self.subtensor.is_hotkey_registered = MagicMock( - side_effect=is_registered_side_effect - ) + self.subtensor.get_neuron_for_pubkey_and_subnet = MagicMock( + return_value=bittensor.NeuronInfo._null_neuron() + ) + self.subtensor.is_hotkey_registered = MagicMock( + side_effect=is_registered_side_effect + ) - self.subtensor.difficulty = MagicMock(return_value=1) - self.subtensor.get_current_block = MagicMock(side_effect=current_block) - self.subtensor._do_pow_register = do_pow_register_mock - - # should return True - self.assertTrue( - self.subtensor.register( - wallet=wallet, netuid=3, num_processes=3, update_interval=5 - ), - msg="Registration should succeed", - ) + self.subtensor.difficulty = MagicMock(return_value=1) + self.subtensor.get_current_block = MagicMock(side_effect=current_block) + self.subtensor._do_pow_register = do_pow_register_mock + + # should return True + self.assertTrue( + self.subtensor.register( + wallet=wallet, netuid=3, num_processes=3, update_interval=5 + ), + msg="Registration should succeed", + ) def test_registration_failed(self): is_registered_return_values = [False for _ in range(100)] @@ -544,7 +515,7 @@ def test_registration_failed(self): mock_neuron.is_null = True with patch( - "bittensor._subtensor.extrinsics.registration.create_pow", return_value=None + "bittensor.extrinsics.registration.create_pow", return_value=None ) as mock_create_pow: wallet = _get_mock_wallet( hotkey=_get_mock_keypair(0, self.id()), @@ -596,32 +567,24 @@ class ExitEarly(Exception): mock_create_pow = MagicMock(return_value=MagicMock(is_stale=mock_is_stale)) - with patch( - "bittensor.Subtensor.get_neuron_for_pubkey_and_subnet", - return_value=bittensor.NeuronInfo._null_neuron(), - ): - with patch( - "bittensor._subtensor.extrinsics.registration.create_pow", - mock_create_pow, - ): - # should create a pow and check if it is stale - # then should create a new pow and check if it is stale - # then should enter substrate and exit early because of test - with pytest.raises(ExitEarly): - bittensor.Subtensor.register( - mock_subtensor_self, mock_wallet, netuid=3 - ) - self.assertEqual( - mock_create_pow.call_count, - 2, - msg="must try another pow after stale", - ) - self.assertEqual(mock_is_stale.call_count, 2) - self.assertEqual( - mock_do_pow_register.call_count, - 1, - msg="only tries to submit once, then exits", - ) + with patch("bittensor.extrinsics.registration.create_pow", mock_create_pow): + # should create a pow and check if it is stale + # then should create a new pow and check if it is stale + # then should enter substrate and exit early because of test + self.subtensor.get_neuron_for_pubkey_and_subnet = MagicMock( + return_value=bittensor.NeuronInfo._null_neuron() + ) + with pytest.raises(ExitEarly): + bittensor.subtensor.register(mock_subtensor_self, mock_wallet, netuid=3) + self.assertEqual( + mock_create_pow.call_count, 2, msg="must try another pow after stale" + ) + self.assertEqual(mock_is_stale.call_count, 2) + self.assertEqual( + mock_do_pow_register.call_count, + 1, + msg="only tries to submit once, then exits", + ) # # This test was flaking, please check to_defaults before reactiving the test diff --git a/tests/unit_tests/bittensor_tests/__init__.py b/tests/unit_tests/bittensor_tests/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/unit_tests/bittensor_tests/test_axon.py b/tests/unit_tests/bittensor_tests/test_axon.py deleted file mode 100644 index 7a79fae57c..0000000000 --- a/tests/unit_tests/bittensor_tests/test_axon.py +++ /dev/null @@ -1,393 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao -# Copyright © 2022 Opentensor Foundation - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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 time -import grpc -import uuid -import unittest -import unittest.mock as mock -from unittest.mock import MagicMock - -import bittensor -from bittensor.utils.test_utils import get_random_unused_port - -from tests.helpers import _get_mock_wallet, _get_mock_keypair - - -def gen_nonce(): - return f"{time.monotonic_ns()}" - - -def sign_v2(sender_wallet, receiver_wallet): - nonce, receptor_uid = gen_nonce(), str(uuid.uuid1()) - sender_hotkey = sender_wallet.hotkey.ss58_address - receiver_hotkey = receiver_wallet.hotkey.ss58_address - message = f"{nonce}.{sender_hotkey}.{receiver_hotkey}.{receptor_uid}" - signature = f"0x{sender_wallet.hotkey.sign(message).hex()}" - return ".".join([nonce, sender_hotkey, signature, receptor_uid]) - - -def sign(sender_wallet, receiver_wallet, receiver_version): - return sign_v2(sender_wallet, receiver_wallet) - - -def is_port_in_use(port): - import socket - - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - val = s.connect_ex(("localhost", port)) - if val == 0: - return True - else: - return False - - -class TestAxon(unittest.TestCase): - @classmethod - def setUpClass(cls) -> None: - cls.wallet = wallet = _get_mock_wallet( - coldkey=_get_mock_keypair(0, cls.__name__), - hotkey=_get_mock_keypair(100 + 0, cls.__name__), - ) - - cls.axon = bittensor.axon(wallet=wallet, metagraph=None) - - cls.sender_wallet = _get_mock_wallet( - coldkey=_get_mock_keypair(1, cls.__name__), - hotkey=_get_mock_keypair(100 + 1, cls.__name__), - ) - - def test_axon_start(self): - mock_wallet = MagicMock( - spec=bittensor.Wallet, - coldkey=MagicMock(), - coldkeypub=MagicMock( - # mock ss58 address - ss58_address="5DD26kC2kxajmwfbbZmVmxhrY9VeeyR1Gpzy9i8wxLUg6zxm" - ), - hotkey=MagicMock( - ss58_address="5CtstubuSoVLJGCXkiWRNKrrGg2DVBZ9qMs2qYTLsZR4q1Wg" - ), - ) - axon = bittensor.axon(wallet=mock_wallet, metagraph=None) - axon.start() - assert axon.server._state.stage == grpc._server._ServerStage.STARTED - - def test_axon_stop(self): - mock_wallet = MagicMock( - spec=bittensor.Wallet, - coldkey=MagicMock(), - coldkeypub=MagicMock( - # mock ss58 address - ss58_address="5DD26kC2kxajmwfbbZmVmxhrY9VeeyR1Gpzy9i8wxLUg6zxm" - ), - hotkey=MagicMock( - ss58_address="5CtstubuSoVLJGCXkiWRNKrrGg2DVBZ9qMs2qYTLsZR4q1Wg" - ), - ) - axon = bittensor.axon(wallet=mock_wallet, metagraph=None) - axon.start() - time.sleep(1) - axon.stop() - time.sleep(1) - assert axon.server._state.stage == grpc._server._ServerStage.STOPPED - - def test_sign_v2(self): - sign_v2(self.sender_wallet, self.wallet) - - def test_axon_is_destroyed(self): - mock_wallet = MagicMock( - spec=bittensor.Wallet, - coldkey=MagicMock(), - coldkeypub=MagicMock( - # mock ss58 address - ss58_address="5DD26kC2kxajmwfbbZmVmxhrY9VeeyR1Gpzy9i8wxLUg6zxm" - ), - hotkey=MagicMock( - ss58_address="5CtstubuSoVLJGCXkiWRNKrrGg2DVBZ9qMs2qYTLsZR4q1Wg" - ), - ) - - port = get_random_unused_port() - assert is_port_in_use(port) == False - axon = bittensor.axon(wallet=mock_wallet, metagraph=None, port=port) - assert is_port_in_use(port) == True - axon.start() - assert is_port_in_use(port) == True - axon.stop() - assert is_port_in_use(port) == False - axon.__del__() - assert is_port_in_use(port) == False - - port = get_random_unused_port() - assert is_port_in_use(port) == False - axon2 = bittensor.axon(wallet=mock_wallet, metagraph=None, port=port) - assert is_port_in_use(port) == True - axon2.start() - assert is_port_in_use(port) == True - axon2.__del__() - assert is_port_in_use(port) == False - - port_3 = get_random_unused_port() - assert is_port_in_use(port_3) == False - axonA = bittensor.axon(wallet=mock_wallet, metagraph=None, port=port_3) - assert is_port_in_use(port_3) == True - axonB = bittensor.axon(wallet=mock_wallet, metagraph=None, port=port_3) - assert axonA.server != axonB.server - assert is_port_in_use(port_3) == True - axonA.start() - assert is_port_in_use(port_3) == True - axonB.start() - assert is_port_in_use(port_3) == True - axonA.__del__() - assert is_port_in_use(port) == False - axonB.__del__() - assert is_port_in_use(port) == False - - -# test external axon args -class TestExternalAxon(unittest.TestCase): - """ - Tests the external axon config flags - `--axon.external_port` and `--axon.external_ip` - Need to verify the external config is used when broadcasting to the network - and the internal config is used when creating the grpc server - - Also test the default behaviour when no external axon config is provided - (should use the internal axon config, like usual) - """ - - def test_external_ip_not_set_dont_use_internal_ip(self): - # Verify that not setting the external ip arg will NOT default to the internal axon ip - mock_add_insecure_port = mock.MagicMock(return_value=None) - mock_server = mock.MagicMock(add_insecure_port=mock_add_insecure_port) - - mock_config = bittensor.axon.config() - mock_wallet = MagicMock( - spec=bittensor.Wallet, - coldkey=MagicMock(), - coldkeypub=MagicMock( - # mock ss58 address - ss58_address="5DD26kC2kxajmwfbbZmVmxhrY9VeeyR1Gpzy9i8wxLUg6zxm" - ), - hotkey=MagicMock( - ss58_address="5CtstubuSoVLJGCXkiWRNKrrGg2DVBZ9qMs2qYTLsZR4q1Wg" - ), - ) - axon = bittensor.axon( - wallet=mock_wallet, - metagraph=None, - ip="fake_ip", - server=mock_server, - config=mock_config, - ) - assert axon.external_ip != axon.ip # should be different - assert (axon.external_ip is None) or ( - axon.external_ip == bittensor.utils.networking.get_external_ip() - ) # should be None OR default from bittensor.utils - - def test_external_port_not_set_use_internal_port(self): - # Verify that not setting the external port arg will default to the internal axon port - mock_config = bittensor.axon.config() - - mock_wallet = mock.MagicMock( - hotkey=mock.MagicMock( - ss58_address="fake_hotkey_address", spec=bittensor.Keypair - ), - spec=bittensor.Wallet, - ) - - with mock.patch("bittensor.wallet") as mock_create_wallet: - mock_create_wallet.return_value = mock_wallet - axon = bittensor.axon( - wallet=mock_wallet, metagraph=None, port=1234, config=mock_config - ) - assert axon.external_port == axon.port - - def test_external_port_set_full_address_internal(self): - internal_port = 1234 - external_port = 5678 - mock_wallet = mock.MagicMock( - hotkey=mock.MagicMock( - ss58_address="fake_hotkey_address", spec=bittensor.Keypair - ), - spec=bittensor.Wallet, - ) - mock_add_insecure_port = mock.MagicMock(return_value=None) - mock_server = mock.MagicMock(add_insecure_port=mock_add_insecure_port) - - mock_config = bittensor.axon.config() - - _ = bittensor.axon( - wallet=mock_wallet, - metagraph=None, - port=internal_port, - external_port=external_port, - server=mock_server, - config=mock_config, - ) - - mock_add_insecure_port.assert_called_once() - args, _ = mock_add_insecure_port.call_args - full_address0 = args[0] - - assert ( - f"{internal_port}" in full_address0 - and f":{external_port}" not in full_address0 - ) - - mock_add_insecure_port.reset_mock() - - # Test using config - mock_config = bittensor.axon.config() - - mock_config.axon.port = internal_port - mock_config.axon.external_port = external_port - - _ = bittensor.axon( - wallet=mock_wallet, metagraph=None, config=mock_config, server=mock_server - ) - - mock_add_insecure_port.assert_called_once() - args, _ = mock_add_insecure_port.call_args - full_address0 = args[0] - - assert ( - f"{internal_port}" in full_address0 - ), f"{internal_port} was not found in {full_address0}" - assert ( - f":{external_port}" not in full_address0 - ), f":{external_port} was found in {full_address0}" - - def test_external_ip_set_full_address_internal(self): - internal_ip = "fake_ip_internal" - external_ip = "fake_ip_external" - - mock_wallet = mock.MagicMock( - hotkey=mock.MagicMock( - ss58_address="fake_hotkey_address", spec=bittensor.Keypair - ), - spec=bittensor.Wallet, - ) - - mock_add_insecure_port = mock.MagicMock(return_value=None) - mock_server = mock.MagicMock(add_insecure_port=mock_add_insecure_port) - - mock_config = bittensor.axon.config() - - _ = bittensor.axon( - wallet=mock_wallet, - metagraph=None, - ip=internal_ip, - external_ip=external_ip, - server=mock_server, - config=mock_config, - ) - - mock_add_insecure_port.assert_called_once() - args, _ = mock_add_insecure_port.call_args - full_address0 = args[0] - - assert ( - f"{internal_ip}" in full_address0 and f"{external_ip}" not in full_address0 - ) - - mock_add_insecure_port.reset_mock() - - # Test using config - mock_config = bittensor.axon.config() - mock_config.axon.external_ip = external_ip - mock_config.axon.ip = internal_ip - - _ = bittensor.axon( - wallet=mock_wallet, metagraph=None, config=mock_config, server=mock_server - ) - - mock_add_insecure_port.assert_called_once() - args, _ = mock_add_insecure_port.call_args - full_address0 = args[0] - - assert ( - f"{internal_ip}" in full_address0 - ), f"{internal_ip} was not found in {full_address0}" - assert ( - f"{external_ip}" not in full_address0 - ), f"{external_ip} was found in {full_address0}" - - def test_external_ip_port_set_full_address_internal(self): - internal_ip = "fake_ip_internal" - external_ip = "fake_ip_external" - internal_port = 1234 - external_port = 5678 - - mock_wallet = mock.MagicMock( - hotkey=mock.MagicMock( - ss58_address="fake_hotkey_address", spec=bittensor.Keypair - ), - spec=bittensor.Wallet, - ) - - mock_add_insecure_port = mock.MagicMock(return_value=None) - mock_server = mock.MagicMock(add_insecure_port=mock_add_insecure_port) - - mock_config = bittensor.axon.config() - - _ = bittensor.axon( - wallet=mock_wallet, - metagraph=None, - ip=internal_ip, - external_ip=external_ip, - port=internal_port, - external_port=external_port, - server=mock_server, - config=mock_config, - ) - - mock_add_insecure_port.assert_called_once() - args, _ = mock_add_insecure_port.call_args - full_address0 = args[0] - - assert ( - f"{internal_ip}:{internal_port}" == full_address0 - and f"{external_ip}:{external_port}" != full_address0 - ) - - mock_add_insecure_port.reset_mock() - - # Test using config - mock_config = bittensor.axon.config() - - mock_config.axon.ip = internal_ip - mock_config.axon.external_ip = external_ip - mock_config.axon.port = internal_port - mock_config.axon.external_port = external_port - - _ = bittensor.axon( - wallet=mock_wallet, metagraph=None, config=mock_config, server=mock_server - ) - - mock_add_insecure_port.assert_called_once() - args, _ = mock_add_insecure_port.call_args - full_address1 = args[0] - - assert ( - f"{internal_ip}:{internal_port}" == full_address1 - ), f"{internal_ip}:{internal_port} is not eq to {full_address1}" - assert ( - f"{external_ip}:{external_port}" != full_address1 - ), f"{external_ip}:{external_port} is eq to {full_address1}" diff --git a/tests/unit_tests/bittensor_tests/test_config.py b/tests/unit_tests/bittensor_tests/test_config.py deleted file mode 100644 index 7eb976f94c..0000000000 --- a/tests/unit_tests/bittensor_tests/test_config.py +++ /dev/null @@ -1,112 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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. - -from multiprocessing.sharedctypes import Value -from sys import prefix -import argparse -import pytest -from unittest.mock import MagicMock - -import bittensor - - -def test_prefix(): - # Test the use of prefixes to instantiate all of the bittensor objects. - parser = argparse.ArgumentParser() - - mock_wallet = MagicMock( - spec=bittensor.Wallet, - coldkey=MagicMock(), - coldkeypub=MagicMock( - # mock ss58 address - ss58_address="5DD26kC2kxajmwfbbZmVmxhrY9VeeyR1Gpzy9i8wxLUg6zxm" - ), - hotkey=MagicMock( - ss58_address="5CtstubuSoVLJGCXkiWRNKrrGg2DVBZ9qMs2qYTLsZR4q1Wg" - ), - ) - - # bittensor.dendrite.add_args( parser) - # bittensor.dendrite.add_args( parser, prefix = 'second' ) - - bittensor.logging.add_args(parser) - bittensor.logging.add_args(parser, prefix="second") - - bittensor.wallet.add_args(parser) - bittensor.wallet.add_args(parser, prefix="second") - - bittensor.subtensor.add_args(parser) - bittensor.subtensor.add_args(parser, prefix="second") - - # bittensor.metagraph.add_args( parser ) - # bittensor.metagraph.add_args( parser, prefix = 'second' ) - - bittensor.dataset.add_args(parser) - bittensor.dataset.add_args(parser, prefix="second") - - bittensor.axon.add_args(parser) - bittensor.axon.add_args(parser, prefix="second") - - # bittensor.wandb.add_args( parser ) - # bittensor.wandb.add_args( parser, prefix = 'second' ) - - # Test with argv=[] - config_non_strict = bittensor.config(parser, strict=False, args=[]) - config_strict = bittensor.config(parser, strict=True, args=[]) - - # bittensor.dendrite( config_strict ).__del__() - # bittensor.dendrite( config_non_strict ).__del__() - # bittensor.dendrite( config_strict.second ).__del__() - # bittensor.dendrite( config_non_strict.second ).__del__() - - bittensor.axon(metagraph=None, wallet=mock_wallet, config=config_strict).stop() - bittensor.axon(metagraph=None, wallet=mock_wallet, config=config_non_strict).stop() - bittensor.axon( - metagraph=None, wallet=mock_wallet, config=config_strict.second - ).stop() - bittensor.axon( - metagraph=None, wallet=mock_wallet, config=config_non_strict.second - ).stop() - - # bittensor.metagraph( config_strict ) - # bittensor.metagraph( config_non_strict ) - # bittensor.metagraph( config_strict.second ) - # bittensor.metagraph( config_non_strict.second ) - - bittensor.wallet(config_strict) - bittensor.wallet(config_non_strict) - bittensor.wallet(config_strict.second) - bittensor.wallet(config_non_strict.second) - - bittensor.logging(config_strict) - bittensor.logging(config_non_strict) - bittensor.logging(config_strict.second) - bittensor.logging(config_non_strict.second) - - # This is the only place we call bittensor.wandb() outside of neuron code. - # It fails because we don't have a key set up for this. - # TODO: Actually test bittensor.wandb - # bittensor.wandb( config_strict ) - # bittensor.wandb( config_non_strict ) - # bittensor.wandb( config_strict.second ) - # bittensor.wandb( config_non_strict.second ) - - -if __name__ == "__main__": - # test_loaded_config() - # test_strict() - test_prefix() diff --git a/tests/unit_tests/bittensor_tests/test_metagraph.py b/tests/unit_tests/bittensor_tests/test_metagraph.py deleted file mode 100644 index cd9f8c3a7f..0000000000 --- a/tests/unit_tests/bittensor_tests/test_metagraph.py +++ /dev/null @@ -1,39 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2023 Opentensor Foundation - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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 bittensor -import unittest - -_subtensor_mock = bittensor.subtensor(network="mock", _mock=True) - - -class TestMetagraph(unittest.TestCase): - def setUp(self) -> None: - global _subtensor_mock - _subtensor_mock.reset() - - _subtensor_mock.create_subnet(netuid=999) - - def test_metagraph(self): - global _subtensor_mock - metagraph = _subtensor_mock.metagraph(netuid=999) - - assert metagraph.netuid == 999 - assert metagraph.n == 0 - assert len(metagraph.hotkeys) == 0 - assert len(metagraph.coldkeys) == 0 - assert len(metagraph.uids) == 0 diff --git a/tests/unit_tests/bittensor_tests/test_serialization.py b/tests/unit_tests/bittensor_tests/test_serialization.py deleted file mode 100644 index 422c6c5f78..0000000000 --- a/tests/unit_tests/bittensor_tests/test_serialization.py +++ /dev/null @@ -1,343 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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 unittest -import pytest -import bittensor - - -class TestSerialization(unittest.TestCase): - def test_serialize(self): - for _ in range(10): - tensor_a = torch.rand([12, 23]) - serializer = bittensor.serializer( - serializer_type=bittensor.proto.Serializer.MSGPACK - ) - content = serializer.serialize( - tensor_a, from_type=bittensor.proto.TensorType.TORCH - ) - tensor_b = serializer.deserialize( - content, to_type=bittensor.proto.TensorType.TORCH - ) - torch.all(torch.eq(tensor_a, tensor_b)) - - def test_serialize_object_type_exception(self): - # Let's grab a random image, and try and de-serialize it incorrectly. - image = torch.ones([1, 28, 28]) - - serializer = bittensor.serializer( - serializer_type=bittensor.proto.Serializer.MSGPACK - ) - with pytest.raises( - bittensor.serializer.SerializationTypeNotImplementedException - ): - serializer.serialize(image, from_type=11) - - def test_deserialization_object_type_exception(self): - data = torch.rand([12, 23]) - - serializer = bittensor.serializer( - serializer_type=bittensor.proto.Serializer.MSGPACK - ) - tensor_message = serializer.serialize( - data, from_type=bittensor.proto.TensorType.TORCH - ) - - with pytest.raises( - bittensor.serializer.SerializationTypeNotImplementedException - ): - serializer.deserialize(tensor_message, to_type=11) - - def test_serialize_deserialize_image(self): - # Let's grab some image data - # Let's grab a random image, and give it a crazy type to break the system - image = torch.ones([1, 28, 28]) - - serializer = bittensor.serializer( - serializer_type=bittensor.proto.Serializer.MSGPACK - ) - serialized_image_tensor_message = serializer.serialize( - image, from_type=bittensor.proto.TensorType.TORCH - ) - - assert image.requires_grad == serialized_image_tensor_message.requires_grad - assert list(image.shape) == serialized_image_tensor_message.shape - assert serialized_image_tensor_message.dtype != bittensor.proto.DataType.UNKNOWN - - deserialized_image_tensor_message = serializer.deserialize( - serialized_image_tensor_message, to_type=bittensor.proto.TensorType.TORCH - ) - assert ( - serialized_image_tensor_message.requires_grad - == deserialized_image_tensor_message.requires_grad - ) - assert serialized_image_tensor_message.shape == list( - deserialized_image_tensor_message.shape - ) - assert ( - bittensor.serializer.torch_dtype_to_bittensor_dtype( - deserialized_image_tensor_message.dtype - ) - != bittensor.proto.DataType.UNKNOWN - ) - - assert torch.all(torch.eq(deserialized_image_tensor_message, image)) - - def test_serialize_deserialize_text(self): - # Let's create some text data - words = ["This", "is", "a", "word", "list"] - max_l = 0 - ts_list = [] - for w in words: - ts_list.append(torch.ByteTensor(list(bytes(w, "utf8")))) - max_l = max(ts_list[-1].size()[0], max_l) - - data = torch.zeros((len(ts_list), max_l), dtype=torch.int64) - for i, ts in enumerate(ts_list): - data[i, 0 : ts.size()[0]] = ts - - serializer = bittensor.serializer( - serializer_type=bittensor.proto.Serializer.MSGPACK - ) - serialized_data_tensor_message = serializer.serialize( - data, from_type=bittensor.proto.TensorType.TORCH - ) - - assert data.requires_grad == serialized_data_tensor_message.requires_grad - assert list(data.shape) == serialized_data_tensor_message.shape - assert serialized_data_tensor_message.dtype != bittensor.proto.DataType.UNKNOWN - - deserialized_data_tensor_message = serializer.deserialize( - serialized_data_tensor_message, to_type=bittensor.proto.TensorType.TORCH - ) - assert ( - serialized_data_tensor_message.requires_grad - == deserialized_data_tensor_message.requires_grad - ) - assert serialized_data_tensor_message.shape == list( - deserialized_data_tensor_message.shape - ) - assert ( - bittensor.serializer.torch_dtype_to_bittensor_dtype( - deserialized_data_tensor_message.dtype - ) - != bittensor.proto.DataType.UNKNOWN - ) - - assert torch.all(torch.eq(deserialized_data_tensor_message, data)) - - def test_serialize_deserialize_tensor(self): - data = torch.rand([12, 23]) - - serializer = bittensor.serializer( - serializer_type=bittensor.proto.Serializer.MSGPACK - ) - serialized_tensor_message = serializer.serialize( - data, from_type=bittensor.proto.TensorType.TORCH - ) - - assert data.requires_grad == serialized_tensor_message.requires_grad - assert list(data.shape) == serialized_tensor_message.shape - assert serialized_tensor_message.dtype == bittensor.proto.DataType.FLOAT32 - - deserialized_tensor_message = serializer.deserialize( - serialized_tensor_message, to_type=bittensor.proto.TensorType.TORCH - ) - assert ( - serialized_tensor_message.requires_grad - == deserialized_tensor_message.requires_grad - ) - assert serialized_tensor_message.shape == list( - deserialized_tensor_message.shape - ) - assert ( - bittensor.serializer.torch_dtype_to_bittensor_dtype( - deserialized_tensor_message.dtype - ) - == bittensor.proto.DataType.FLOAT32 - ) - - assert torch.all(torch.eq(deserialized_tensor_message, data)) - - def test_bittensor_dtype_to_torch_dtype(self): - with pytest.raises(bittensor.serializer.DeserializationException): - bittensor.serializer.bittensor_dtype_to_torch_dtype(11) - - -class TestCMPSerialization(unittest.TestCase): - def test_serialize(self): - for _ in range(10): - tensor_a = torch.rand([12, 23]) - serializer = bittensor.serializer( - serializer_type=bittensor.proto.Serializer.CMPPACK - ) - content = serializer.serialize( - tensor_a, from_type=bittensor.proto.TensorType.TORCH - ) - tensor_b = serializer.deserialize( - content, to_type=bittensor.proto.TensorType.TORCH - ) - torch.all(torch.eq(tensor_a, tensor_b)) - - def test_serialize_object_type_exception(self): - # Let's grab a random image, and try and de-serialize it incorrectly. - image = torch.ones([1, 28, 28]) - - serializer = bittensor.serializer( - serializer_type=bittensor.proto.Serializer.CMPPACK - ) - with pytest.raises( - bittensor.serializer.SerializationTypeNotImplementedException - ): - serializer.serialize(image, from_type=11) - - def test_deserialization_object_type_exception(self): - data = torch.rand([12, 23]) - - serializer = bittensor.serializer( - serializer_type=bittensor.proto.Serializer.CMPPACK - ) - tensor_message = serializer.serialize( - data, from_type=bittensor.proto.TensorType.TORCH - ) - - with pytest.raises( - bittensor.serializer.SerializationTypeNotImplementedException - ): - serializer.deserialize(tensor_message, to_type=11) - - def test_serialize_deserialize_image(self): - # Let's grab some image data - # Let's grab a random image, and give it a crazy type to break the system - image = torch.ones([1, 28, 28]) - data_size = image.element_size() * image.nelement() - - serializer = bittensor.serializer( - serializer_type=bittensor.proto.Serializer.CMPPACK - ) - serialized_image_tensor_message = serializer.serialize( - image, from_type=bittensor.proto.TensorType.TORCH - ) - - assert image.requires_grad == serialized_image_tensor_message.requires_grad - assert list(image.shape) == serialized_image_tensor_message.shape - assert serialized_image_tensor_message.dtype != bittensor.proto.DataType.UNKNOWN - assert serialized_image_tensor_message.ByteSize() < data_size - - deserialized_image_tensor_message = serializer.deserialize( - serialized_image_tensor_message, to_type=bittensor.proto.TensorType.TORCH - ) - assert ( - serialized_image_tensor_message.requires_grad - == deserialized_image_tensor_message.requires_grad - ) - assert serialized_image_tensor_message.shape == list( - deserialized_image_tensor_message.shape - ) - assert ( - bittensor.serializer.torch_dtype_to_bittensor_dtype( - deserialized_image_tensor_message.dtype - ) - != bittensor.proto.DataType.UNKNOWN - ) - - assert torch.all(torch.eq(deserialized_image_tensor_message, image)) - - def test_serialize_deserialize_text(self): - # Let's create some text data - words = ["This", "is", "a", "word", "list"] - max_l = 0 - ts_list = [] - for w in words: - ts_list.append(torch.ByteTensor(list(bytes(w, "utf8")))) - max_l = max(ts_list[-1].size()[0], max_l) - - data = torch.zeros((len(ts_list), max_l), dtype=torch.int64) - for i, ts in enumerate(ts_list): - data[i, 0 : ts.size()[0]] = ts - data_size = data.element_size() * data.nelement() - - serializer = bittensor.serializer( - serializer_type=bittensor.proto.Serializer.CMPPACK - ) - serialized_data_tensor_message = serializer.serialize( - data, from_type=bittensor.proto.TensorType.TORCH - ) - - assert data.requires_grad == serialized_data_tensor_message.requires_grad - assert list(data.shape) == serialized_data_tensor_message.shape - assert serialized_data_tensor_message.dtype != bittensor.proto.DataType.UNKNOWN - assert serialized_data_tensor_message.ByteSize() < data_size - - deserialized_data_tensor_message = serializer.deserialize( - serialized_data_tensor_message, to_type=bittensor.proto.TensorType.TORCH - ) - assert ( - serialized_data_tensor_message.requires_grad - == deserialized_data_tensor_message.requires_grad - ) - assert serialized_data_tensor_message.shape == list( - deserialized_data_tensor_message.shape - ) - assert ( - bittensor.serializer.torch_dtype_to_bittensor_dtype( - deserialized_data_tensor_message.dtype - ) - != bittensor.proto.DataType.UNKNOWN - ) - - assert torch.all(torch.eq(deserialized_data_tensor_message, data)) - - def test_serialize_deserialize_tensor(self): - data = torch.rand([12, 23]) - data_size = data.element_size() * data.nelement() - - serializer = bittensor.serializer( - serializer_type=bittensor.proto.Serializer.CMPPACK - ) - serialized_tensor_message = serializer.serialize( - data, from_type=bittensor.proto.TensorType.TORCH - ) - - assert data.requires_grad == serialized_tensor_message.requires_grad - assert list(data.shape) == serialized_tensor_message.shape - assert serialized_tensor_message.dtype == bittensor.proto.DataType.FLOAT32 - assert serialized_tensor_message.ByteSize() < data_size - - deserialized_tensor_message = serializer.deserialize( - serialized_tensor_message, to_type=bittensor.proto.TensorType.TORCH - ) - assert ( - serialized_tensor_message.requires_grad - == deserialized_tensor_message.requires_grad - ) - assert serialized_tensor_message.shape == list( - deserialized_tensor_message.shape - ) - assert ( - bittensor.serializer.torch_dtype_to_bittensor_dtype( - deserialized_tensor_message.dtype - ) - == bittensor.proto.DataType.FLOAT32 - ) - - assert torch.all(torch.eq(deserialized_tensor_message, data.to(torch.float16))) - - def test_bittensor_dtype_to_torch_dtype(self): - with pytest.raises(bittensor.serializer.DeserializationException): - bittensor.serializer.bittensor_dtype_to_torch_dtype(11) diff --git a/tests/unit_tests/bittensor_tests/test_synapse.py b/tests/unit_tests/bittensor_tests/test_synapse.py deleted file mode 100644 index 172044433f..0000000000 --- a/tests/unit_tests/bittensor_tests/test_synapse.py +++ /dev/null @@ -1,143 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao -# Copyright © 2022 Opentensor Foundation - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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 bittensor -import torch -import unittest -from unittest.mock import MagicMock - - -class MockTextPromptingSynapse(bittensor.TextPromptingSynapse): - def forward(self, messages): - return messages - - def multi_forward(self, messages): - return messages - - def backward(self, messages, response, rewards): - return messages, response, rewards - - def priority(self, call: bittensor.SynapseCall) -> float: - return 0.0 - - def blacklist(self, call: bittensor.SynapseCall) -> bool: - return False - - -def test_create_text_prompting(): - mock_wallet = MagicMock( - spec=bittensor.Wallet, - coldkey=MagicMock(), - coldkeypub=MagicMock( - # mock ss58 address - ss58_address="5DD26kC2kxajmwfbbZmVmxhrY9VeeyR1Gpzy9i8wxLUg6zxm" - ), - hotkey=MagicMock( - ss58_address="5CtstubuSoVLJGCXkiWRNKrrGg2DVBZ9qMs2qYTLsZR4q1Wg" - ), - ) - axon = bittensor.axon(wallet=mock_wallet, metagraph=None) - synapse = MockTextPromptingSynapse(axon=axon) - - -# @unittest.skip("This is for convenience of testing without violating DRY too much") -def get_synapse(): - mock_wallet = MagicMock( - spec=bittensor.Wallet, - coldkey=MagicMock(), - coldkeypub=MagicMock( - # mock ss58 address - ss58_address="5DD26kC2kxajmwfbbZmVmxhrY9VeeyR1Gpzy9i8wxLUg6zxm" - ), - hotkey=MagicMock( - ss58_address="5CtstubuSoVLJGCXkiWRNKrrGg2DVBZ9qMs2qYTLsZR4q1Wg" - ), - ) - axon = bittensor.axon(wallet=mock_wallet, metagraph=None) - return MockTextPromptingSynapse(axon=axon) - - -def test_text_prompting_synapse_forward(): - synapse = get_synapse() - messages = ["test message"] - response = synapse.forward(messages) - assert response == messages - - -def test_text_prompting_synapse_multi_forward(): - synapse = get_synapse() - messages = ["test message"] * 10 - responses = synapse.multi_forward(messages) - assert responses == messages - - -def test_text_prompting_synapse_backward(): - synapse = get_synapse() - messages = ["test message"] - response = ["test response"] - rewards = torch.tensor([1.0]) - output = synapse.backward(messages, response, rewards) - assert len(output) == 3 - assert messages == output[0] - assert response == output[1] - assert torch.all(torch.eq(rewards, output[2])) - - -def test_text_prompting_synapse_blacklist(): - synapse = get_synapse() - request = bittensor.proto.ForwardTextPromptingRequest() - - # Mock the signature checking of the context. - context = MagicMock() - context.invocation_metadata.return_value = {} - synapse.axon = MagicMock() - synapse.axon.auth_interceptor = MagicMock() - synapse.axon.auth_interceptor.parse_signature.return_value = ( - None, - None, - "5CtstubuSoVLJGCXkiWRNKrrGg2DVBZ9qMs2qYTLsZR4q1Wg", - None, - ) - - call = bittensor._synapse.text_prompting.synapse.SynapseForward( - synapse, request, synapse.forward, context=context - ) - blacklist = synapse.blacklist(call) - assert blacklist == False - - -def test_text_prompting_synapse_priority(): - synapse = get_synapse() - request = bittensor.proto.ForwardTextPromptingRequest() - - # Mock the signature checking of the context. - context = MagicMock() - context.invocation_metadata.return_value = {} - synapse.axon = MagicMock() - synapse.axon.auth_interceptor = MagicMock() - synapse.axon.auth_interceptor.parse_signature.return_value = ( - None, - None, - "5CtstubuSoVLJGCXkiWRNKrrGg2DVBZ9qMs2qYTLsZR4q1Wg", - None, - ) - - call = bittensor._synapse.text_prompting.synapse.SynapseForward( - synapse, request, synapse.forward, context=context - ) - priority = synapse.priority(call) - assert priority == 0.0 diff --git a/tests/unit_tests/bittensor_tests/utils/__init__.py b/tests/unit_tests/bittensor_tests/utils/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/unit_tests/test_axon.py b/tests/unit_tests/test_axon.py new file mode 100644 index 0000000000..a8e57e17a3 --- /dev/null +++ b/tests/unit_tests/test_axon.py @@ -0,0 +1,241 @@ +# The MIT License (MIT) +# Copyright © 2021 Yuma Rao +# Copyright © 2022 Opentensor Foundation +# Copyright © 2023 Opentensor Technologies Inc + +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the “Software”), to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of +# the Software. + +# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# 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 pytest +import unittest +import bittensor +from typing import Any +from unittest import IsolatedAsyncioTestCase +from starlette.requests import Request +from unittest.mock import MagicMock +from bittensor.axon import AxonMiddleware + + +def test_attach(): + # Create a mock AxonServer instance + server = bittensor.axon() + + # Define the Synapse type + class Synapse(bittensor.Synapse): + pass + + # Define the functions with the correct signatures + def forward_fn(synapse: Synapse) -> Any: + pass + + def blacklist_fn(synapse: Synapse) -> bool: + return True + + def priority_fn(synapse: Synapse) -> float: + return 1.0 + + def verify_fn(synapse: Synapse) -> None: + pass + + # Test attaching with correct signatures + server.attach(forward_fn, blacklist_fn, priority_fn, verify_fn) + + # Define functions with incorrect signatures + def wrong_blacklist_fn(synapse: Synapse) -> int: + return 1 + + def wrong_priority_fn(synapse: Synapse) -> int: + return 1 + + def wrong_verify_fn(synapse: Synapse) -> bool: + return True + + # Test attaching with incorrect signatures + with pytest.raises(AssertionError): + server.attach(forward_fn, wrong_blacklist_fn, priority_fn, verify_fn) + + with pytest.raises(AssertionError): + server.attach(forward_fn, blacklist_fn, wrong_priority_fn, verify_fn) + + with pytest.raises(AssertionError): + server.attach(forward_fn, blacklist_fn, priority_fn, wrong_verify_fn) + + +def test_attach(): + # Create a mock AxonServer instance + server = bittensor.axon() + + # Define the Synapse type + class Synapse: + pass + + # Define a class that inherits from Synapse + class InheritedSynapse(bittensor.Synapse): + pass + + # Define a function with the correct signature + def forward_fn(synapse: InheritedSynapse) -> Any: + pass + + # Test attaching with correct signature and inherited class + server.attach(forward_fn) + + # Define a class that does not inherit from Synapse + class NonInheritedSynapse: + pass + + # Define a function with an argument of a class not inheriting from Synapse + def wrong_forward_fn(synapse: NonInheritedSynapse) -> Any: + pass + + # Test attaching with incorrect class inheritance + with pytest.raises(AssertionError): + server.attach(wrong_forward_fn) + + +# Mock synapse class for testing + + +class AxonMock: + def __init__(self): + self.status_code = None + self.forward_class_types = {} + self.blacklist_fns = {} + self.priority_fns = {} + self.forward_fns = {} + self.verify_fns = {} + self.thread_pool = bittensor.PriorityThreadPoolExecutor(max_workers=1) + + +class SynapseMock(bittensor.Synapse): + pass + + +def verify_fn_pass(synapse): + pass + + +def verify_fn_fail(synapse): + raise Exception("Verification failed") + + +def blacklist_fn_pass(synapse): + return False, "" + + +def blacklist_fn_fail(synapse): + return True, "" + + +def priority_fn_pass(synapse) -> float: + return 0.0 + + +def priority_fn_timeout(synapse) -> float: + return 2.0 + + +@pytest.fixture +def middleware(): + # Mock AxonMiddleware instance with empty axon object + axon = AxonMock() + return AxonMiddleware(None, axon) + + +@pytest.mark.asyncio +async def test_verify_pass(middleware): + synapse = SynapseMock() + middleware.axon.verify_fns = {"SynapseMock": verify_fn_pass} + await middleware.verify(synapse) + assert synapse.axon.status_code != 401 + + +@pytest.mark.asyncio +async def test_verify_fail(middleware): + synapse = SynapseMock() + middleware.axon.verify_fns = {"SynapseMock": verify_fn_fail} + with pytest.raises(Exception): + await middleware.verify(synapse) + assert synapse.axon.status_code == 401 + + +@pytest.mark.asyncio +async def test_blacklist_pass(middleware): + synapse = SynapseMock() + middleware.axon.blacklist_fns = {"SynapseMock": blacklist_fn_pass} + await middleware.blacklist(synapse) + assert synapse.axon.status_code != 403 + + +@pytest.mark.asyncio +async def test_blacklist_fail(middleware): + synapse = SynapseMock() + middleware.axon.blacklist_fns = {"SynapseMock": blacklist_fn_fail} + with pytest.raises(Exception): + await middleware.blacklist(synapse) + assert synapse.axon.status_code == 403 + + +@pytest.mark.asyncio +async def test_priority_pass(middleware): + synapse = SynapseMock() + middleware.axon.priority_fns = {"SynapseMock": priority_fn_pass} + await middleware.priority(synapse) + assert synapse.axon.status_code != 408 + + +class TestAxonMiddleware(IsolatedAsyncioTestCase): + def setUp(self): + # Create a mock app + self.mock_app = MagicMock() + # Create a mock axon + self.mock_axon = MagicMock() + self.mock_axon.uuid = "1234" + self.mock_axon.forward_class_types = { + "request_name": bittensor.Synapse, + } + self.mock_axon.wallet.hotkey.sign.return_value = bytes.fromhex("aabbccdd") + # Create an instance of AxonMiddleware + self.axon_middleware = AxonMiddleware(self.mock_app, self.mock_axon) + return self.axon_middleware + + @pytest.mark.asyncio + async def test_preprocess(self): + # Mock the request + request = MagicMock(spec=Request) + request.url.path = "/request_name" + request.client.port = "5000" + request.client.host = "192.168.0.1" + request.headers = {} + + synapse = await self.axon_middleware.preprocess(request) + + # Check if the preprocess function fills the axon information into the synapse + assert synapse.axon.version == str(bittensor.__version_as_int__) + assert synapse.axon.uuid == "1234" + assert synapse.axon.nonce is not None + assert synapse.axon.status_message == "Success" + assert synapse.axon.status_code == "100" + assert synapse.axon.signature == "0xaabbccdd" + + # Check if the preprocess function fills the dendrite information into the synapse + assert synapse.dendrite.port == "5000" + assert synapse.dendrite.ip == "192.168.0.1" + + # Check if the preprocess function sets the request name correctly + assert synapse.name == "request_name" + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/unit_tests/test_dendrite.py b/tests/unit_tests/test_dendrite.py new file mode 100644 index 0000000000..d61a9faf79 --- /dev/null +++ b/tests/unit_tests/test_dendrite.py @@ -0,0 +1,110 @@ +# The MIT License (MIT) +# Copyright © 2022 Yuma Rao +# Copyright © 2022-2023 Opentensor Foundation +# Copyright © 2023 Opentensor Technologies Inc + +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the “Software”), to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of +# the Software. + +# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# 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 pytest +import bittensor +from unittest.mock import MagicMock, Mock, patch +from tests.helpers import _get_mock_wallet + + +@pytest.fixture +def setup_dendrite(): + user_wallet = ( + _get_mock_wallet() + ) # assuming bittensor.wallet() returns a wallet object + dendrite_obj = bittensor.dendrite(user_wallet) + return dendrite_obj + + +def test_init(setup_dendrite): + dendrite_obj = setup_dendrite + assert isinstance(dendrite_obj, bittensor.dendrite) + assert dendrite_obj.keypair == setup_dendrite.keypair + + +def test_str(setup_dendrite): + dendrite_obj = setup_dendrite + expected_string = "dendrite({})".format(setup_dendrite.keypair.ss58_address) + assert str(dendrite_obj) == expected_string + + +def test_repr(setup_dendrite): + dendrite_obj = setup_dendrite + expected_string = "dendrite({})".format(setup_dendrite.keypair.ss58_address) + assert repr(dendrite_obj) == expected_string + + +class AsyncMock(Mock): + def __call__(self, *args, **kwargs): + sup = super(AsyncMock, self) + + async def coro(): + return sup.__call__(*args, **kwargs) + + return coro() + + def __await__(self): + return self().__await__() + + +def test_dendrite_create_wallet(): + d = bittensor.dendrite(_get_mock_wallet()) + d = bittensor.dendrite(_get_mock_wallet().hotkey) + d = bittensor.dendrite(_get_mock_wallet().coldkeypub) + assert d.__str__() == d.__repr__() + + +@pytest.mark.asyncio +async def test_forward_many(): + n = 10 + d = bittensor.dendrite(wallet=_get_mock_wallet()) + d.call = AsyncMock() + axons = [MagicMock() for _ in range(n)] + + resps = await d(axons) + assert len(resps) == n + resp = await d(axons[0]) + assert len([resp]) == 1 + + resps = await d.forward(axons) + assert len(resps) == n + resp = await d.forward(axons[0]) + assert len([resp]) == 1 + + +def test_pre_process_synapse(): + d = bittensor.dendrite(wallet=_get_mock_wallet()) + s = bittensor.Synapse() + synapse = d.preprocess_synapse_for_request( + target_axon_info=bittensor.axon(wallet=_get_mock_wallet()).info(), + synapse=s, + timeout=12, + ) + assert synapse.timeout == 12 + assert synapse.dendrite + assert synapse.axon + assert synapse.dendrite.ip + assert synapse.dendrite.version + assert synapse.dendrite.nonce + assert synapse.dendrite.uuid + assert synapse.dendrite.hotkey + assert synapse.axon.ip + assert synapse.axon.port + assert synapse.axon.hotkey + assert synapse.dendrite.signature diff --git a/tests/unit_tests/test_keyfile.py b/tests/unit_tests/test_keyfile.py new file mode 100644 index 0000000000..49373bfa3c --- /dev/null +++ b/tests/unit_tests/test_keyfile.py @@ -0,0 +1,478 @@ +# The MIT License (MIT) +# Copyright © 2022 Opentensor Foundation + +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the “Software”), to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of +# the Software. + +# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# 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 os +import time +import pytest +import shutil +import unittest +import bittensor +import unittest.mock as mock +from scalecodec import ScaleBytes +from substrateinterface import Keypair, KeypairType +from substrateinterface.constants import DEV_PHRASE +from substrateinterface.exceptions import ConfigurationError +from bip39 import bip39_validate + + +class KeyPairTestCase(unittest.TestCase): + """ + Test case for the KeyPair class. + """ + + def test_generate_mnemonic(self): + """ + Test the generation of a mnemonic and its validation. + """ + mnemonic = Keypair.generate_mnemonic() + self.assertTrue(bip39_validate(mnemonic)) + + def test_invalid_mnemonic(self): + """ + Test the validation of an invalid mnemonic. + """ + mnemonic = "This is an invalid mnemonic" + self.assertFalse(bip39_validate(mnemonic)) + + def test_create_sr25519_keypair(self): + """ + Test the creation of a sr25519 keypair from a mnemonic and verify the SS58 address. + """ + mnemonic = "old leopard transfer rib spatial phone calm indicate online fire caution review" + keypair = Keypair.create_from_mnemonic(mnemonic, ss58_format=0) + self.assertEqual( + keypair.ss58_address, "16ADqpMa4yzfmWs3nuTSMhfZ2ckeGtvqhPWCNqECEGDcGgU2" + ) + + def test_only_provide_ss58_address(self): + """ + Test the creation of a keypair with only the SS58 address provided. + """ + keypair = Keypair( + ss58_address="16ADqpMa4yzfmWs3nuTSMhfZ2ckeGtvqhPWCNqECEGDcGgU2" + ) + self.assertEqual( + "0x" + keypair.public_key.hex(), + "0xe4359ad3e2716c539a1d663ebd0a51bdc5c98a12e663bb4c4402db47828c9446", + ) + + def test_only_provide_public_key(self): + """ + Test the creation of a keypair with only the public key provided. + """ + keypair = Keypair( + public_key="0xe4359ad3e2716c539a1d663ebd0a51bdc5c98a12e663bb4c4402db47828c9446", + ss58_format=0, + ) + self.assertEqual( + keypair.ss58_address, "16ADqpMa4yzfmWs3nuTSMhfZ2ckeGtvqhPWCNqECEGDcGgU2" + ) + + def test_provide_no_ss58_address_and_public_key(self): + """ + Test the creation of a keypair without providing SS58 address and public key. + """ + self.assertRaises(ValueError, Keypair) + + def test_incorrect_private_key_length_sr25519(self): + """ + Test the creation of a keypair with an incorrect private key length for sr25519. + """ + self.assertRaises( + ValueError, + Keypair, + private_key="0x23", + ss58_address="16ADqpMa4yzfmWs3nuTSMhfZ2ckeGtvqhPWCNqECEGDcGgU2", + ) + + def test_incorrect_public_key(self): + """ + Test the creation of a keypair with an incorrect public key. + """ + self.assertRaises(ValueError, Keypair, public_key="0x23") + + def test_sign_and_verify(self): + """ + Test the signing and verification of a message using a keypair. + """ + mnemonic = Keypair.generate_mnemonic() + keypair = Keypair.create_from_mnemonic(mnemonic) + signature = keypair.sign("Test1231223123123") + self.assertTrue(keypair.verify("Test1231223123123", signature)) + + def test_sign_and_verify_hex_data(self): + """ + Test the signing and verification of hex data using a keypair. + """ + mnemonic = Keypair.generate_mnemonic() + keypair = Keypair.create_from_mnemonic(mnemonic) + signature = keypair.sign("0x1234") + self.assertTrue(keypair.verify("0x1234", signature)) + + def test_sign_and_verify_scale_bytes(self): + """ + Test the signing and verification of ScaleBytes data using a keypair. + """ + mnemonic = Keypair.generate_mnemonic() + keypair = Keypair.create_from_mnemonic(mnemonic) + data = ScaleBytes("0x1234") + signature = keypair.sign(data) + self.assertTrue(keypair.verify(data, signature)) + + def test_sign_missing_private_key(self): + """ + Test signing a message with a keypair that is missing the private key. + """ + keypair = Keypair( + ss58_address="5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" + ) + self.assertRaises(ConfigurationError, keypair.sign, "0x1234") + + def test_sign_unsupported_crypto_type(self): + """ + Test signing a message with an unsupported crypto type. + """ + keypair = Keypair.create_from_private_key( + ss58_address="16ADqpMa4yzfmWs3nuTSMhfZ2ckeGtvqhPWCNqECEGDcGgU2", + private_key="0x1f1995bdf3a17b60626a26cfe6f564b337d46056b7a1281b64c649d592ccda0a9cffd34d9fb01cae1fba61aeed184c817442a2186d5172416729a4b54dd4b84e", + crypto_type=3, + ) + self.assertRaises(ConfigurationError, keypair.sign, "0x1234") + + def test_verify_unsupported_crypto_type(self): + """ + Test verifying a signature with an unsupported crypto type. + """ + keypair = Keypair.create_from_private_key( + ss58_address="16ADqpMa4yzfmWs3nuTSMhfZ2ckeGtvqhPWCNqECEGDcGgU2", + private_key="0x1f1995bdf3a17b60626a26cfe6f564b337d46056b7a1281b64c649d592ccda0a9cffd34d9fb01cae1fba61aeed184c817442a2186d5172416729a4b54dd4b84e", + crypto_type=3, + ) + self.assertRaises(ConfigurationError, keypair.verify, "0x1234", "0x1234") + + def test_sign_and_verify_incorrect_signature(self): + """ + Test verifying an incorrect signature for a signed message. + """ + mnemonic = Keypair.generate_mnemonic() + keypair = Keypair.create_from_mnemonic(mnemonic) + signature = "0x4c291bfb0bb9c1274e86d4b666d13b2ac99a0bacc04a4846fb8ea50bda114677f83c1f164af58fc184451e5140cc8160c4de626163b11451d3bbb208a1889f8a" + self.assertFalse(keypair.verify("Test1231223123123", signature)) + + def test_sign_and_verify_invalid_signature(self): + """ + Test verifying an invalid signature format for a signed message. + """ + mnemonic = Keypair.generate_mnemonic() + keypair = Keypair.create_from_mnemonic(mnemonic) + signature = "Test" + self.assertRaises(TypeError, keypair.verify, "Test1231223123123", signature) + + def test_sign_and_verify_invalid_message(self): + """ + Test verifying a signature against an incorrect message. + """ + mnemonic = Keypair.generate_mnemonic() + keypair = Keypair.create_from_mnemonic(mnemonic) + signature = keypair.sign("Test1231223123123") + self.assertFalse(keypair.verify("OtherMessage", signature)) + + def test_create_ed25519_keypair(self): + """ + Test the creation of an ed25519 keypair from a mnemonic and verify the SS58 address. + """ + mnemonic = "old leopard transfer rib spatial phone calm indicate online fire caution review" + keypair = Keypair.create_from_mnemonic( + mnemonic, ss58_format=0, crypto_type=KeypairType.ED25519 + ) + self.assertEqual( + keypair.ss58_address, "16dYRUXznyhvWHS1ktUENGfNAEjCawyDzHRtN9AdFnJRc38h" + ) + + def test_sign_and_verify_ed25519(self): + """ + Test the signing and verification of a message using an ed25519 keypair. + """ + mnemonic = Keypair.generate_mnemonic() + keypair = Keypair.create_from_mnemonic( + mnemonic, crypto_type=KeypairType.ED25519 + ) + signature = keypair.sign("Test1231223123123") + self.assertTrue(keypair.verify("Test1231223123123", signature)) + + def test_sign_and_verify_invalid_signature_ed25519(self): + """ + Test verifying an incorrect signature for a message signed with an ed25519 keypair. + """ + mnemonic = Keypair.generate_mnemonic() + keypair = Keypair.create_from_mnemonic( + mnemonic, crypto_type=KeypairType.ED25519 + ) + signature = "0x4c291bfb0bb9c1274e86d4b666d13b2ac99a0bacc04a4846fb8ea50bda114677f83c1f164af58fc184451e5140cc8160c4de626163b11451d3bbb208a1889f8a" + self.assertFalse(keypair.verify("Test1231223123123", signature)) + + def test_unsupport_crypto_type(self): + """ + Test creating a keypair with an unsupported crypto type. + """ + self.assertRaises( + ValueError, + Keypair.create_from_seed, + seed_hex="0xda3cf5b1e9144931?a0f0db65664aab662673b099415a7f8121b7245fb0be4143", + crypto_type=2, + ) + + def test_create_keypair_from_private_key(self): + """ + Test creating a keypair from a private key and verify the public key. + """ + keypair = Keypair.create_from_private_key( + ss58_address="16ADqpMa4yzfmWs3nuTSMhfZ2ckeGtvqhPWCNqECEGDcGgU2", + private_key="0x1f1995bdf3a17b60626a26cfe6f564b337d46056b7a1281b64c649d592ccda0a9cffd34d9fb01cae1fba61aeed184c817442a2186d5172416729a4b54dd4b84e", + ) + self.assertEqual( + "0x" + keypair.public_key.hex(), + "0xe4359ad3e2716c539a1d663ebd0a51bdc5c98a12e663bb4c4402db47828c9446", + ) + + def test_hdkd_hard_path(self): + """ + Test hierarchical deterministic key derivation with a hard derivation path. + """ + mnemonic = "old leopard transfer rib spatial phone calm indicate online fire caution review" + derivation_address = "5FEiH8iuDUw271xbqWTWuB6WrDjv5dnCeDX1CyHubAniXDNN" + derivation_path = "//Alice" + derived_keypair = Keypair.create_from_uri(mnemonic + derivation_path) + self.assertEqual(derivation_address, derived_keypair.ss58_address) + + def test_hdkd_soft_path(self): + """ + Test hierarchical deterministic key derivation with a soft derivation path. + """ + mnemonic = "old leopard transfer rib spatial phone calm indicate online fire caution review" + derivation_address = "5GNXbA46ma5dg19GXdiKi5JH3mnkZ8Yea3bBtZAvj7t99P9i" + derivation_path = "/Alice" + derived_keypair = Keypair.create_from_uri(mnemonic + derivation_path) + self.assertEqual(derivation_address, derived_keypair.ss58_address) + + def test_hdkd_default_to_dev_mnemonic(self): + """ + Test hierarchical deterministic key derivation with a default development mnemonic. + """ + derivation_address = "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" + derivation_path = "//Alice" + derived_keypair = Keypair.create_from_uri(derivation_path) + self.assertEqual(derivation_address, derived_keypair.ss58_address) + + def test_hdkd_nested_hard_soft_path(self): + """ + Test hierarchical deterministic key derivation with a nested hard and soft derivation path. + """ + derivation_address = "5CJGwWiKXSE16WJaxBdPZhWqUYkotgenLUALv7ZvqQ4TXeqf" + derivation_path = "//Bob/test" + derived_keypair = Keypair.create_from_uri(derivation_path) + self.assertEqual(derivation_address, derived_keypair.ss58_address) + + def test_hdkd_nested_soft_hard_path(self): + """ + Test hierarchical deterministic key derivation with a nested soft and hard derivation path. + """ + derivation_address = "5Cwc8tShrshDJUp1P1M21dKUTcYQpV9GcfSa4hUBNmMdV3Cx" + derivation_path = "/Bob//test" + derived_keypair = Keypair.create_from_uri(derivation_path) + self.assertEqual(derivation_address, derived_keypair.ss58_address) + + def test_hdkd_path_gt_32_bytes(self): + """ + Test hierarchical deterministic key derivation with a derivation path longer than 32 bytes. + """ + derivation_address = "5GR5pfZeNs1uQiSWVxZaQiZou3wdZiX894eqgvfNfHbEh7W2" + derivation_path = "//PathNameLongerThan32BytesWhichShouldBeHashed" + derived_keypair = Keypair.create_from_uri(derivation_path) + self.assertEqual(derivation_address, derived_keypair.ss58_address) + + def test_hdkd_unsupported_password(self): + """ + Test hierarchical deterministic key derivation with an unsupported password. + """ + self.assertRaises( + NotImplementedError, Keypair.create_from_uri, DEV_PHRASE + "///test" + ) + + +class TestKeyFiles(unittest.TestCase): + def setUp(self) -> None: + self.root_path = f"/tmp/pytest{time.time()}" + os.makedirs(self.root_path, exist_ok=True) + + self.create_keyfile() + + def tearDown(self) -> None: + shutil.rmtree(self.root_path) + + def create_keyfile(self): + keyfile = bittensor.keyfile(path=os.path.join(self.root_path, "keyfile")) + + mnemonic = bittensor.Keypair.generate_mnemonic(12) + alice = bittensor.Keypair.create_from_mnemonic(mnemonic) + keyfile.set_keypair( + alice, encrypt=True, overwrite=True, password="thisisafakepassword" + ) + + bob = bittensor.Keypair.create_from_uri("/Bob") + keyfile.set_keypair( + bob, encrypt=True, overwrite=True, password="thisisafakepassword" + ) + + return keyfile + + def test_create(self): + keyfile = bittensor.keyfile(path=os.path.join(self.root_path, "keyfile")) + + mnemonic = bittensor.Keypair.generate_mnemonic(12) + alice = bittensor.Keypair.create_from_mnemonic(mnemonic) + keyfile.set_keypair( + alice, encrypt=True, overwrite=True, password="thisisafakepassword" + ) + assert keyfile.is_readable() + assert keyfile.is_writable() + assert keyfile.is_encrypted() + keyfile.decrypt(password="thisisafakepassword") + assert not keyfile.is_encrypted() + keyfile.encrypt(password="thisisafakepassword") + assert keyfile.is_encrypted() + str(keyfile) + keyfile.decrypt(password="thisisafakepassword") + assert not keyfile.is_encrypted() + str(keyfile) + + assert ( + keyfile.get_keypair(password="thisisafakepassword").ss58_address + == alice.ss58_address + ) + assert ( + keyfile.get_keypair(password="thisisafakepassword").private_key + == alice.private_key + ) + assert ( + keyfile.get_keypair(password="thisisafakepassword").public_key + == alice.public_key + ) + + bob = bittensor.Keypair.create_from_uri("/Bob") + keyfile.set_keypair( + bob, encrypt=True, overwrite=True, password="thisisafakepassword" + ) + assert ( + keyfile.get_keypair(password="thisisafakepassword").ss58_address + == bob.ss58_address + ) + assert ( + keyfile.get_keypair(password="thisisafakepassword").public_key + == bob.public_key + ) + + repr(keyfile) + + def test_legacy_coldkey(self): + legacy_filename = os.path.join(self.root_path, "coldlegacy_keyfile") + keyfile = bittensor.keyfile(path=legacy_filename) + keyfile.make_dirs() + keyfile_data = ( + b"0x32939b6abc4d81f02dff04d2b8d1d01cc8e71c5e4c7492e4fa6a238cdca3512f" + ) + with open(legacy_filename, "wb") as keyfile_obj: + keyfile_obj.write(keyfile_data) + assert keyfile.keyfile_data == keyfile_data + keyfile.encrypt(password="this is the fake password") + keyfile.decrypt(password="this is the fake password") + keypair_bytes = b'{"accountId": "0x32939b6abc4d81f02dff04d2b8d1d01cc8e71c5e4c7492e4fa6a238cdca3512f", "publicKey": "0x32939b6abc4d81f02dff04d2b8d1d01cc8e71c5e4c7492e4fa6a238cdca3512f", "secretPhrase": null, "secretSeed": null, "ss58Address": "5DD26kC2kxajmwfbbZmVmxhrY9VeeyR1Gpzy9i8wxLUg6zxm"}' + assert keyfile.keyfile_data == keypair_bytes + assert ( + keyfile.get_keypair().ss58_address + == "5DD26kC2kxajmwfbbZmVmxhrY9VeeyR1Gpzy9i8wxLUg6zxm" + ) + assert ( + "0x" + keyfile.get_keypair().public_key.hex() + == "0x32939b6abc4d81f02dff04d2b8d1d01cc8e71c5e4c7492e4fa6a238cdca3512f" + ) + + def test_validate_password(self): + from bittensor.keyfile import validate_password + + assert validate_password(None) == False + assert validate_password("passw0rd") == False + assert validate_password("123456789") == False + with mock.patch("getpass.getpass", return_value="biTTensor"): + assert validate_password("biTTensor") == True + with mock.patch("getpass.getpass", return_value="biTTenso"): + assert validate_password("biTTensor") == False + + def test_decrypt_keyfile_data_legacy(self): + import base64 + + from cryptography.fernet import Fernet + from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives import hashes + from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC + + from bittensor.keyfile import decrypt_keyfile_data + + __SALT = b"Iguesscyborgslikemyselfhaveatendencytobeparanoidaboutourorigins" + + def __generate_key(password): + kdf = PBKDF2HMAC( + algorithm=hashes.SHA256(), + salt=__SALT, + length=32, + iterations=10000000, + backend=default_backend(), + ) + key = base64.urlsafe_b64encode(kdf.derive(password.encode())) + return key + + pw = "fakepasssword238947239" + data = b"encrypt me!" + key = __generate_key(pw) + cipher_suite = Fernet(key) + encrypted_data = cipher_suite.encrypt(data) + + decrypted_data = decrypt_keyfile_data(encrypted_data, pw) + assert decrypted_data == data + + def test_user_interface(self): + from bittensor.keyfile import ask_password_to_encrypt + + with mock.patch( + "getpass.getpass", + side_effect=["pass", "password", "asdury3294y", "asdury3294y"], + ): + assert ask_password_to_encrypt() == "asdury3294y" + + def test_overwriting(self): + keyfile = bittensor.keyfile(path=os.path.join(self.root_path, "keyfile")) + alice = bittensor.Keypair.create_from_uri("/Alice") + keyfile.set_keypair( + alice, encrypt=True, overwrite=True, password="thisisafakepassword" + ) + bob = bittensor.Keypair.create_from_uri("/Bob") + + with pytest.raises(bittensor.KeyFileError) as pytest_wrapped_e: + with mock.patch("builtins.input", return_value="n"): + keyfile.set_keypair( + bob, encrypt=True, overwrite=False, password="thisisafakepassword" + ) diff --git a/tests/unit_tests/test_metagraph.py b/tests/unit_tests/test_metagraph.py new file mode 100644 index 0000000000..167a0a375d --- /dev/null +++ b/tests/unit_tests/test_metagraph.py @@ -0,0 +1,118 @@ +# The MIT License (MIT) +# Copyright © 2023 Opentensor Technologies Inc + +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the “Software”), to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of +# the Software. + +# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# 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 unittest +from unittest.mock import Mock + +import bittensor +import torch + + +class TestMetagraph(unittest.TestCase): + def setUp(self): + # Mock the subtensor and neurons + self.subtensor = Mock() + self.neurons = [ + Mock( + uid=i, + trust=i + 0.5, + consensus=i + 0.1, + incentive=i + 0.2, + dividends=i + 0.3, + rank=i + 0.4, + emission=i + 0.5, + active=i, + last_update=i, + validator_permit=True if i % 2 == 0 else False, + validator_trust=i + 0.6, + total_stake=Mock(tao=i + 0.7), + stake=i + 0.8, + axon_info="axon_info_{}".format(i), + weights=[(j, j + 0.1) for j in range(5)], # Add some mock weights + bonds=[(j, j + 0.2) for j in range(5)], # Add some mock bonds + ) + for i in range(10) + ] + + def test_set_metagraph_attributes(self): + metagraph = bittensor.metagraph(1, sync=False) + metagraph.neurons = self.neurons + metagraph._set_metagraph_attributes(block=5, subtensor=self.subtensor) + + # Check the attributes are set as expected + self.assertEqual(metagraph.n.item(), len(self.neurons)) + self.assertEqual(metagraph.block.item(), 5) + self.assertTrue( + torch.equal( + metagraph.uids, + torch.tensor( + [neuron.uid for neuron in self.neurons], dtype=torch.int64 + ), + ) + ) + self.assertTrue( + torch.equal( + metagraph.trust, + torch.tensor( + [neuron.trust for neuron in self.neurons], dtype=torch.float32 + ), + ) + ) + self.assertTrue( + torch.equal( + metagraph.consensus, + torch.tensor( + [neuron.consensus for neuron in self.neurons], dtype=torch.float32 + ), + ) + ) + # Similarly for other attributes... + + # Test the axons + self.assertEqual(metagraph.axons, [n.axon_info for n in self.neurons]) + + def test_process_weights_or_bonds(self): + metagraph = bittensor.metagraph(1, sync=False) + metagraph.neurons = self.neurons + + # Test weights processing + weights = metagraph._process_weights_or_bonds( + data=[neuron.weights for neuron in self.neurons], attribute="weights" + ) + self.assertEqual( + weights.shape[0], len(self.neurons) + ) # Number of rows should be equal to number of neurons + self.assertEqual( + weights.shape[1], len(self.neurons) + ) # Number of columns should be equal to number of neurons + # TODO: Add more checks to ensure the weights have been processed correctly + + # Test bonds processing + bonds = metagraph._process_weights_or_bonds( + data=[neuron.bonds for neuron in self.neurons], attribute="bonds" + ) + self.assertEqual( + bonds.shape[0], len(self.neurons) + ) # Number of rows should be equal to number of neurons + self.assertEqual( + bonds.shape[1], len(self.neurons) + ) # Number of columns should be equal to number of neurons + # TODO: Add more checks to ensure the bonds have been processed correctly + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/unit_tests/bittensor_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py similarity index 87% rename from tests/unit_tests/bittensor_tests/test_subtensor.py rename to tests/unit_tests/test_subtensor.py index d126b7777e..cddedc9752 100644 --- a/tests/unit_tests/bittensor_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -34,17 +34,11 @@ def test_serve_axon_with_external_ip_set(self): mock_serve_axon = MagicMock(return_value=True) - mock_subtensor = MagicMock( - spec=bittensor.Subtensor, - # serve=mock_serve, - serve_axon=mock_serve_axon, - ) + mock_subtensor = MagicMock(spec=bittensor.subtensor, serve_axon=mock_serve_axon) mock_add_insecure_port = mock.MagicMock(return_value=None) - mock_grpc_server = mock.MagicMock(add_insecure_port=mock_add_insecure_port) - mock_wallet = MagicMock( - spec=bittensor.Wallet, + spec=bittensor.wallet, coldkey=MagicMock(), coldkeypub=MagicMock( # mock ss58 address @@ -58,15 +52,14 @@ def test_serve_axon_with_external_ip_set(self): mock_config = bittensor.axon.config() mock_axon_with_external_ip_set = bittensor.axon( wallet=mock_wallet, - metagraph=None, ip=internal_ip, external_ip=external_ip, - server=mock_grpc_server, config=mock_config, ) mock_subtensor.serve_axon( - netuid=-1, axon=mock_axon_with_external_ip_set, use_upnpc=False + netuid=-1, + axon=mock_axon_with_external_ip_set, ) mock_serve_axon.assert_called_once() @@ -87,11 +80,13 @@ def test_serve_axon_with_external_port_set(self): mock_serve_axon = MagicMock(return_value=True) mock_subtensor = MagicMock( - spec=bittensor.Subtensor, serve=mock_serve, serve_axon=mock_serve_axon + spec=bittensor.subtensor, + serve=mock_serve, + serve_axon=mock_serve_axon, ) mock_wallet = MagicMock( - spec=bittensor.Wallet, + spec=bittensor.wallet, coldkey=MagicMock(), coldkeypub=MagicMock( # mock ss58 address @@ -103,16 +98,12 @@ def test_serve_axon_with_external_port_set(self): ) mock_add_insecure_port = mock.MagicMock(return_value=None) - mock_grpc_server = mock.MagicMock(add_insecure_port=mock_add_insecure_port) - mock_config = bittensor.axon.config() mock_axon_with_external_port_set = bittensor.axon( wallet=mock_wallet, - metagraph=None, port=internal_port, external_port=external_port, - server=mock_grpc_server, config=mock_config, ) @@ -121,7 +112,8 @@ def test_serve_axon_with_external_port_set(self): ): # mock the get_external_ip function to return the external ip mock_subtensor.serve_axon( - netuid=-1, axon=mock_axon_with_external_port_set, use_upnpc=False + netuid=-1, + axon=mock_axon_with_external_port_set, ) mock_serve_axon.assert_called_once() @@ -146,7 +138,7 @@ def test_stake_multiple(self): mock_amount: bittensor.Balance = bittensor.Balance.from_tao(1.0) mock_wallet = MagicMock( - spec=bittensor.Wallet, + spec=bittensor.wallet, coldkey=MagicMock(), coldkeypub=MagicMock( # mock ss58 address @@ -168,7 +160,7 @@ def test_stake_multiple(self): mock_do_stake = MagicMock(side_effect=ExitEarly) mock_subtensor = MagicMock( - spec=bittensor.Subtensor, + spec=bittensor.subtensor, network="mock_net", get_balance=MagicMock( return_value=bittensor.Balance.from_tao(mock_amount.tao + 20.0) @@ -178,7 +170,7 @@ def test_stake_multiple(self): ) with pytest.raises(ExitEarly): - bittensor.Subtensor.add_stake_multiple( + bittensor.subtensor.add_stake_multiple( mock_subtensor, wallet=mock_wallet, hotkey_ss58s=mock_hotkey_ss58s, diff --git a/tests/unit_tests/test_synapse.py b/tests/unit_tests/test_synapse.py new file mode 100644 index 0000000000..c2900511e1 --- /dev/null +++ b/tests/unit_tests/test_synapse.py @@ -0,0 +1,265 @@ +# The MIT License (MIT) +# Copyright © 2022 Opentensor Foundation + +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the “Software”), to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of +# the Software. + +# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# 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 json +import torch +import base64 +import typing +import pytest +import bittensor + + +def test_parse_headers_to_inputs(): + class Test(bittensor.Synapse): + key1: typing.List[int] + key2: bittensor.Tensor + + # Define a mock headers dictionary to use for testing + headers = { + "bt_header_axon_nonce": "111", + "bt_header_dendrite_ip": "12.1.1.2", + "bt_header_input_obj_key1": base64.b64encode( + json.dumps([1, 2, 3, 4]).encode("utf-8") + ).decode("utf-8"), + "bt_header_tensor_key2": "[3]-torch.float32", + "timeout": "12", + "name": "Test", + "header_size": "111", + "total_size": "111", + "computed_body_hash": "0xabcdef", + } + print(headers) + + # Run the function to test + inputs_dict = Test.parse_headers_to_inputs(headers) + print(inputs_dict) + # Check the resulting dictionary + assert inputs_dict == { + "axon": {"nonce": "111"}, + "dendrite": {"ip": "12.1.1.2"}, + "key1": [1, 2, 3, 4], + "key2": bittensor.Tensor(dtype="torch.float32", shape=[3]), + "timeout": "12", + "name": "Test", + "header_size": "111", + "total_size": "111", + "computed_body_hash": "0xabcdef", + } + + +def test_from_headers(): + class Test(bittensor.Synapse): + key1: typing.List[int] + key2: bittensor.Tensor + + # Define a mock headers dictionary to use for testing + headers = { + "bt_header_axon_nonce": "111", + "bt_header_dendrite_ip": "12.1.1.2", + "bt_header_input_obj_key1": base64.b64encode( + json.dumps([1, 2, 3, 4]).encode("utf-8") + ).decode("utf-8"), + "bt_header_tensor_key2": "[3]-torch.float32", + "timeout": "12", + "name": "Test", + "header_size": "111", + "total_size": "111", + "computed_body_hash": "0xabcdef", + } + + # Run the function to test + synapse = Test.from_headers(headers) + + # Check that the resulting object is an instance of YourClass + assert isinstance(synapse, Test) + + # Check the properties of the resulting object + # Replace with actual checks based on the structure of your class + assert synapse.axon.nonce == 111 + assert synapse.dendrite.ip == "12.1.1.2" + assert synapse.key1 == [1, 2, 3, 4] + assert synapse.key2.shape == [3] + assert synapse.key2.dtype == "torch.float32" + assert synapse.timeout == 12 + assert synapse.name == "Test" + assert synapse.header_size == 111 + assert synapse.total_size == 111 + + +def test_synapse_create(): + # Create an instance of Synapse + synapse = bittensor.Synapse() + + # Ensure the instance created is of type Synapse + assert isinstance(synapse, bittensor.Synapse) + + # Check default properties of a newly created Synapse + assert synapse.name == "Synapse" + assert synapse.timeout == 12.0 + assert synapse.header_size == 0 + assert synapse.total_size == 0 + + # Convert the Synapse instance to a headers dictionary + headers = synapse.to_headers() + + # Ensure the headers is a dictionary and contains the expected keys + assert isinstance(headers, dict) + assert "timeout" in headers + assert "name" in headers + assert "header_size" in headers + assert "total_size" in headers + + # Ensure the 'name' and 'timeout' values match the Synapse's properties + assert headers["name"] == "Synapse" + assert headers["timeout"] == "12.0" + + # Create a new Synapse from the headers and check its 'timeout' property + next_synapse = synapse.from_headers(synapse.to_headers()) + assert next_synapse.timeout == 12.0 + + +def test_custom_synapse(): + # Define a custom Synapse subclass + class Test(bittensor.Synapse): + a: int # Carried through because required. + b: int = None # Not carried through headers + c: typing.Optional[int] # Not carried through headers + d: typing.Optional[typing.List[int]] # Not carried through headers + e: typing.List[int] # Carried through headers + f: bittensor.Tensor # Carried through headers, but not buffer. + + # Create an instance of the custom Synapse subclass + synapse = Test( + a=1, + c=3, + d=[1, 2, 3, 4], + e=[1, 2, 3, 4], + f=bittensor.Tensor.serialize(torch.randn(10)), + ) + + # Ensure the instance created is of type Test and has the expected properties + assert isinstance(synapse, Test) + assert synapse.name == "Test" + assert synapse.a == 1 + assert synapse.b == None + assert synapse.c == 3 + assert synapse.d == [1, 2, 3, 4] + assert synapse.e == [1, 2, 3, 4] + assert synapse.f.shape == [10] + + # Convert the Test instance to a headers dictionary + headers = synapse.to_headers() + + # Ensure the headers contains 'a' but not 'b' + assert "bt_header_input_obj_a" in headers + assert "bt_header_input_obj_b" not in headers + + # Create a new Test from the headers and check its properties + next_synapse = synapse.from_headers(synapse.to_headers()) + assert next_synapse.a == 0 # Default value is 0 + assert next_synapse.b == None + assert next_synapse.c == None + assert next_synapse.d == None + assert next_synapse.e == [] # Empty list is default for list types + assert next_synapse.f.shape == [10] # Shape is passed through + assert next_synapse.f.dtype == "torch.float32" # Type is passed through + assert next_synapse.f.buffer == None # Buffer is not passed through + + +def test_list_tensors(): + class Test(bittensor.Synapse): + a: typing.List[bittensor.Tensor] + + synapse = Test( + a=[bittensor.Tensor.serialize(torch.randn(10))], + ) + headers = synapse.to_headers() + assert "bt_header_list_tensor_a" in headers + assert headers["bt_header_list_tensor_a"] == "['[10]-torch.float32']" + next_synapse = synapse.from_headers(synapse.to_headers()) + assert next_synapse.a[0].dtype == "torch.float32" + assert next_synapse.a[0].shape == [10] + + class Test(bittensor.Synapse): + a: typing.List[bittensor.Tensor] + + synapse = Test( + a=[ + bittensor.Tensor.serialize(torch.randn(10)), + bittensor.Tensor.serialize(torch.randn(11)), + bittensor.Tensor.serialize(torch.randn(12)), + ], + ) + headers = synapse.to_headers() + assert "bt_header_list_tensor_a" in headers + assert ( + headers["bt_header_list_tensor_a"] + == "['[10]-torch.float32', '[11]-torch.float32', '[12]-torch.float32']" + ) + next_synapse = synapse.from_headers(synapse.to_headers()) + assert next_synapse.a[0].dtype == "torch.float32" + assert next_synapse.a[0].shape == [10] + assert next_synapse.a[1].dtype == "torch.float32" + assert next_synapse.a[1].shape == [11] + assert next_synapse.a[2].dtype == "torch.float32" + assert next_synapse.a[2].shape == [12] + + +def test_dict_tensors(): + class Test(bittensor.Synapse): + a: typing.Dict[str, bittensor.Tensor] + + synapse = Test( + a={ + "cat": bittensor.tensor(torch.randn(10)), + "dog": bittensor.tensor(torch.randn(11)), + }, + ) + headers = synapse.to_headers() + assert "bt_header_dict_tensor_a" in headers + assert ( + headers["bt_header_dict_tensor_a"] + == "['cat-[10]-torch.float32', 'dog-[11]-torch.float32']" + ) + next_synapse = synapse.from_headers(synapse.to_headers()) + assert next_synapse.a["cat"].dtype == "torch.float32" + assert next_synapse.a["cat"].shape == [10] + assert next_synapse.a["dog"].dtype == "torch.float32" + assert next_synapse.a["dog"].shape == [11] + + +def test_body_hash_override(): + # Create a Synapse instance + synapse_instance = bittensor.Synapse() + + # Try to set the body_hash property and expect an AttributeError + with pytest.raises( + AttributeError, + match="body_hash property is read-only and cannot be overridden.", + ): + synapse_instance.body_hash = [] + + +def test_required_fields_override(): + # Create a Synapse instance + synapse_instance = bittensor.Synapse() + + # Try to set the required_hash_fields property and expect a TypeError + with pytest.raises( + TypeError, + match='"required_hash_fields" has allow_mutation set to False and cannot be assigned', + ): + synapse_instance.required_hash_fields = [] diff --git a/tests/unit_tests/test_tensor.py b/tests/unit_tests/test_tensor.py new file mode 100644 index 0000000000..c09d1d64e0 --- /dev/null +++ b/tests/unit_tests/test_tensor.py @@ -0,0 +1,137 @@ +# The MIT License (MIT) +# Copyright © 2022 Opentensor Foundation + +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the “Software”), to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of +# the Software. + +# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# 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 pytest +import torch +import bittensor +import numpy + + +# This is a fixture that creates an example tensor for testing +@pytest.fixture +def example_tensor(): + # Create a tensor from a list using PyTorch + data = torch.tensor([1, 2, 3, 4]) + + # Serialize the tensor into a Tensor instance and return it + return bittensor.tensor(data) + + +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) + assert tensor.tolist() == [1, 2, 3, 4] + + +def test_serialize(example_tensor): + # Check that the serialized tensor is an instance of Tensor + assert isinstance(example_tensor, bittensor.Tensor) + + # Check that the Tensor instance has the correct buffer, dtype, and shape + assert example_tensor.buffer == example_tensor.buffer + assert example_tensor.dtype == example_tensor.dtype + assert example_tensor.shape == example_tensor.shape + + assert isinstance(example_tensor.tolist(), list) + + # Check that the Tensor instance has the correct buffer, dtype, and shape + assert example_tensor.buffer == example_tensor.buffer + assert example_tensor.dtype == example_tensor.dtype + assert example_tensor.shape == example_tensor.shape + + assert isinstance(example_tensor.numpy(), numpy.ndarray) + + # Check that the Tensor instance has the correct buffer, dtype, and shape + assert example_tensor.buffer == example_tensor.buffer + assert example_tensor.dtype == example_tensor.dtype + assert example_tensor.shape == example_tensor.shape + + assert isinstance(example_tensor.tensor(), torch.Tensor) + + # Check that the Tensor instance has the correct buffer, dtype, and shape + assert example_tensor.buffer == example_tensor.buffer + assert example_tensor.dtype == example_tensor.dtype + assert example_tensor.shape == example_tensor.shape + + +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] + ) + + # Check that the buffer field matches the provided value + assert tensor.buffer == "0x321e13edqwds231231231232131" + + +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] + ) + + # Check that the dtype field matches the provided value + assert tensor.dtype == "torch.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] + ) + + # Check that the shape field matches the provided value + assert tensor.shape == [3, 3] + + +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)) + + +def test_serialize_all_types_equality(): + torchtensor = torch.randn([100], dtype=torch.float16) + assert torch.all(bittensor.tensor(torchtensor).tensor() == torchtensor) + + torchtensor = torch.randn([100], dtype=torch.float32) + assert torch.all(bittensor.tensor(torchtensor).tensor() == torchtensor) + + torchtensor = torch.randn([100], dtype=torch.float64) + assert torch.all(bittensor.tensor(torchtensor).tensor() == torchtensor) + + torchtensor = torch.randint(255, 256, (1000,), dtype=torch.uint8) + assert torch.all(bittensor.tensor(torchtensor).tensor() == torchtensor) + + torchtensor = torch.randint( + 2_147_483_646, 2_147_483_647, (1000,), dtype=torch.int32 + ) + assert torch.all(bittensor.tensor(torchtensor).tensor() == torchtensor) + + torchtensor = torch.randint( + 9_223_372_036_854_775_806, 9_223_372_036_854_775_807, (1000,), dtype=torch.int64 + ) + assert torch.all(bittensor.tensor(torchtensor).tensor() == torchtensor) + + torchtensor = torch.randn([100], dtype=torch.float32) < 0.5 + assert torch.all(bittensor.tensor(torchtensor).tensor() == torchtensor) diff --git a/tests/unit_tests/test_wallet.py b/tests/unit_tests/test_wallet.py new file mode 100644 index 0000000000..1c27d427bf --- /dev/null +++ b/tests/unit_tests/test_wallet.py @@ -0,0 +1,388 @@ +# The MIT License (MIT) +# Copyright © 2022 Opentensor Foundation + +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the “Software”), to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of +# the Software. + +# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# 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 json +import time +import pytest +import random +import getpass +import unittest +import bittensor +from rich.prompt import Confirm +from ansible_vault import Vault +from unittest.mock import patch, MagicMock + + +class TestWalletUpdate(unittest.TestCase): + def setUp(self): + self.default_updated_password = "nacl_password" + self.default_legacy_password = "ansible_password" + self.empty_wallet = bittensor.wallet(name=f"mock-empty-{str(time.time())}") + self.legacy_wallet = self.create_legacy_wallet() + self.wallet = self.create_wallet() + + def legacy_encrypt_keyfile_data(keyfile_data: bytes, password: str = None) -> bytes: + console = bittensor.__console__ + with console.status(":locked_with_key: Encrypting key..."): + vault = Vault(password) + return vault.vault.encrypt(keyfile_data) + + def create_wallet(self): + # create an nacl wallet + wallet = bittensor.wallet(name=f"mock-{str(time.time())}") + with patch.object( + bittensor, + "ask_password_to_encrypt", + return_value=self.default_updated_password, + ): + wallet.create() + assert "NaCl" in str(wallet.coldkey_file) + + return wallet + + def create_legacy_wallet(self, legacy_password=None): + def _legacy_encrypt_keyfile_data(*args, **kwargs): + args = { + k: v + for k, v in zip( + self.legacy_encrypt_keyfile_data.__code__.co_varnames[: len(args)], + args, + ) + } + kwargs = {**args, **kwargs} + kwargs["password"] = legacy_password + return TestWalletUpdate.legacy_encrypt_keyfile_data(**kwargs) + + legacy_wallet = bittensor.wallet(name=f"mock-legacy-{str(time.time())}") + legacy_password = ( + self.default_legacy_password if legacy_password == None else legacy_password + ) + + # create a legacy ansible wallet + with patch.object( + bittensor, + "encrypt_keyfile_data", + new=_legacy_encrypt_keyfile_data, + # new = TestWalletUpdate.legacy_encrypt_keyfile_data, + ): + legacy_wallet.create() + assert "Ansible" in str(legacy_wallet.coldkey_file) + + return legacy_wallet + + def test_encrypt_and_decrypt(self): + """Test message can be encrypted and decrypted successfully with ansible/nacl.""" + json_data = { + "address": "This is the address.", + "id": "This is the id.", + "key": "This is the key.", + } + message = json.dumps(json_data).encode() + + # encrypt and decrypt with nacl + encrypted_message = bittensor.encrypt_keyfile_data(message, "password") + decrypted_message = bittensor.decrypt_keyfile_data( + encrypted_message, "password" + ) + assert decrypted_message == message + assert bittensor.keyfile_data_is_encrypted(encrypted_message) + assert not bittensor.keyfile_data_is_encrypted(decrypted_message) + assert not bittensor.keyfile_data_is_encrypted_ansible(decrypted_message) + assert bittensor.keyfile_data_is_encrypted_nacl(encrypted_message) + + # encrypt and decrypt with legacy ansible + encrypted_message = TestWalletUpdate.legacy_encrypt_keyfile_data( + message, "password" + ) + decrypted_message = bittensor.decrypt_keyfile_data( + encrypted_message, "password" + ) + assert decrypted_message == message + assert bittensor.keyfile_data_is_encrypted(encrypted_message) + assert not bittensor.keyfile_data_is_encrypted(decrypted_message) + assert not bittensor.keyfile_data_is_encrypted_nacl(decrypted_message) + assert bittensor.keyfile_data_is_encrypted_ansible(encrypted_message) + + def test_check_and_update_encryption_not_updated(self): + """Test for a few cases where wallet should not be updated. + 1. When the wallet is already updated. + 2. When it is the hotkey. + 3. When the wallet is empty. + 4. When the wallet is legacy but no prompt to ask for password. + 5. When the password is wrong. + """ + # test the checking with no rewriting needs to be done. + with patch("bittensor.encrypt_keyfile_data") as encrypt: + # self.wallet is already the most updated with nacl encryption. + assert self.wallet.coldkey_file.check_and_update_encryption() + + # hotkey_file is not encrypted, thus do not need to be updated. + assert not self.wallet.hotkey_file.check_and_update_encryption() + + # empty_wallet has not been created, thus do not need to be updated. + assert not self.empty_wallet.coldkey_file.check_and_update_encryption() + + # legacy wallet cannot be updated without asking for password form prompt. + assert not self.legacy_wallet.coldkey_file.check_and_update_encryption( + no_prompt=True + ) + + # Wrong password + legacy_wallet = self.create_legacy_wallet() + with patch("getpass.getpass", return_value="wrong_password"), patch.object( + Confirm, "ask", return_value=False + ): + assert not legacy_wallet.coldkey_file.check_and_update_encryption() + + # no renewal has been done in this test. + assert not encrypt.called + + def test_check_and_update_excryption(self, legacy_wallet=None): + """Test for the alignment of the updated VS old wallet. + 1. Same coldkey_file data. + 2. Same coldkey path. + 3. Same hotkey_file data. + 4. Same hotkey path. + 5. same password. + + Read the updated wallet in 2 ways. + 1. Directly as the output of check_and_update_encryption() + 2. Read from file using the same coldkey and hotkey name + """ + + def check_new_coldkey_file(keyfile): + new_keyfile_data = keyfile._read_keyfile_data_from_file() + new_decrypted_keyfile_data = bittensor.decrypt_keyfile_data( + new_keyfile_data, legacy_password + ) + new_path = legacy_wallet.coldkey_file.path + + assert old_coldkey_file_data != None + assert new_keyfile_data != None + assert not old_coldkey_file_data == new_keyfile_data + assert bittensor.keyfile_data_is_encrypted_ansible(old_coldkey_file_data) + assert bittensor.keyfile_data_is_encrypted_nacl(new_keyfile_data) + assert not bittensor.keyfile_data_is_encrypted_nacl(old_coldkey_file_data) + assert not bittensor.keyfile_data_is_encrypted_ansible(new_keyfile_data) + assert old_decrypted_coldkey_file_data == new_decrypted_keyfile_data + assert new_path == old_coldkey_path + + def check_new_hotkey_file(keyfile): + new_keyfile_data = keyfile._read_keyfile_data_from_file() + new_path = legacy_wallet.hotkey_file.path + + assert old_hotkey_file_data == new_keyfile_data + assert new_path == old_hotkey_path + assert not bittensor.keyfile_data_is_encrypted(new_keyfile_data) + + if legacy_wallet == None: + legacy_password = f"PASSword-{random.randint(0, 10000)}" + legacy_wallet = self.create_legacy_wallet(legacy_password=legacy_password) + + else: + legacy_password = self.default_legacy_password + + # get old cold keyfile data + old_coldkey_file_data = ( + legacy_wallet.coldkey_file._read_keyfile_data_from_file() + ) + old_decrypted_coldkey_file_data = bittensor.decrypt_keyfile_data( + old_coldkey_file_data, legacy_password + ) + old_coldkey_path = legacy_wallet.coldkey_file.path + + # get old hot keyfile data + old_hotkey_file_data = legacy_wallet.hotkey_file._read_keyfile_data_from_file() + old_hotkey_path = legacy_wallet.hotkey_file.path + + # update legacy_wallet from ansible to nacl + with patch("getpass.getpass", return_value=legacy_password), patch.object( + Confirm, "ask", return_value=True + ): + legacy_wallet.coldkey_file.check_and_update_encryption() + + # get new keyfile data from the same legacy wallet + check_new_coldkey_file(legacy_wallet.coldkey_file) + check_new_hotkey_file(legacy_wallet.hotkey_file) + + # get new keyfile data from wallet name + updated_legacy_wallet = bittensor.wallet( + name=legacy_wallet.name, hotkey=legacy_wallet.hotkey_str + ) + check_new_coldkey_file(updated_legacy_wallet.coldkey_file) + check_new_hotkey_file(updated_legacy_wallet.hotkey_file) + + # def test_password_retain(self): + # [tick] test the same password works + # [tick] try to read using the same hotkey/coldkey name + # [tick] test the same keyfile data could be retained + # [tick] test what if a wrong password was inserted + # [no need] try to read from the new file path + # [tick] test the old and new encrypted is not the same + # [tick] test that the hotkeys are not affected + + +class TestWallet(unittest.TestCase): + def setUp(self): + self.mock_wallet = bittensor.wallet( + name=f"mock-{str(time.time())}", + hotkey=f"mock-{str(time.time())}", + path="/tmp/tests_wallets/do_not_use", + ) + self.mock_wallet.create_new_coldkey( + use_password=False, overwrite=True, suppress=True + ) + self.mock_wallet.create_new_hotkey( + use_password=False, overwrite=True, suppress=True + ) + + def test_regen_coldkeypub_from_ss58_addr(self): + """Test the `regenerate_coldkeypub` method of the wallet class, which regenerates the cold key pair from an SS58 address. + It checks whether the `set_coldkeypub` method is called with the expected arguments, and verifies that the generated key pair's SS58 address matches the input SS58 address. + It also tests the behavior when an invalid SS58 address is provided, raising a `ValueError` as expected. + """ + ss58_address = "5DD26kC2kxajmwfbbZmVmxhrY9VeeyR1Gpzy9i8wxLUg6zxm" + with patch.object(self.mock_wallet, "set_coldkeypub") as mock_set_coldkeypub: + self.mock_wallet.regenerate_coldkeypub( + ss58_address=ss58_address, overwrite=True, suppress=True + ) + + mock_set_coldkeypub.assert_called_once() + keypair: bittensor.Keypair = mock_set_coldkeypub.call_args_list[0][0][0] + self.assertEqual(keypair.ss58_address, ss58_address) + + ss58_address_bad = ( + "5DD26kC2kxajmwfbbZmVmxhrY9VeeyR1Gpzy9i8wxLUg6zx" # 1 character short + ) + with pytest.raises(ValueError): + self.mock_wallet.regenerate_coldkeypub( + ss58_address=ss58_address_bad, overwrite=True, suppress=True + ) + + def test_regen_coldkeypub_from_hex_pubkey_str(self): + """Test the `regenerate_coldkeypub` method of the wallet class, which regenerates the cold key pair from a hex public key string. + It checks whether the `set_coldkeypub` method is called with the expected arguments, and verifies that the generated key pair's public key matches the input public key. + It also tests the behavior when an invalid public key string is provided, raising a `ValueError` as expected. + """ + pubkey_str = ( + "0x32939b6abc4d81f02dff04d2b8d1d01cc8e71c5e4c7492e4fa6a238cdca3512f" + ) + with patch.object(self.mock_wallet, "set_coldkeypub") as mock_set_coldkeypub: + self.mock_wallet.regenerate_coldkeypub( + public_key=pubkey_str, overwrite=True, suppress=True + ) + + mock_set_coldkeypub.assert_called_once() + keypair: bittensor.Keypair = mock_set_coldkeypub.call_args_list[0][0][0] + self.assertEqual("0x" + keypair.public_key.hex(), pubkey_str) + + pubkey_str_bad = "0x32939b6abc4d81f02dff04d2b8d1d01cc8e71c5e4c7492e4fa6a238cdca3512" # 1 character short + with pytest.raises(ValueError): + self.mock_wallet.regenerate_coldkeypub( + ss58_address=pubkey_str_bad, overwrite=True, suppress=True + ) + + def test_regen_coldkeypub_from_hex_pubkey_bytes(self): + """Test the `regenerate_coldkeypub` method of the wallet class, which regenerates the cold key pair from a hex public key byte string. + It checks whether the `set_coldkeypub` method is called with the expected arguments, and verifies that the generated key pair's public key matches the input public key. + """ + pubkey_str = ( + "0x32939b6abc4d81f02dff04d2b8d1d01cc8e71c5e4c7492e4fa6a238cdca3512f" + ) + pubkey_bytes = bytes.fromhex(pubkey_str[2:]) # Remove 0x from beginning + with patch.object(self.mock_wallet, "set_coldkeypub") as mock_set_coldkeypub: + self.mock_wallet.regenerate_coldkeypub( + public_key=pubkey_bytes, overwrite=True, suppress=True + ) + + mock_set_coldkeypub.assert_called_once() + keypair: bittensor.Keypair = mock_set_coldkeypub.call_args_list[0][0][0] + self.assertEqual(keypair.public_key, pubkey_bytes) + + def test_regen_coldkeypub_no_pubkey(self): + """Test the `regenerate_coldkeypub` method of the wallet class when no public key is provided. + It verifies that a `ValueError` is raised when neither a public key nor an SS58 address is provided. + """ + with pytest.raises(ValueError): + # Must provide either public_key or ss58_address + self.mock_wallet.regenerate_coldkeypub( + ss58_address=None, public_key=None, overwrite=True, suppress=True + ) + + def test_regen_coldkey_from_hex_seed_str(self): + """Test the `regenerate_coldkey` method of the wallet class, which regenerates the cold key pair from a hex seed string. + It checks whether the `set_coldkey` method is called with the expected arguments, and verifies that the generated key pair's seed and SS58 address match the input seed and the expected SS58 address. + It also tests the behavior when an invalid seed string is provided, raising a `ValueError` as expected. + """ + ss58_addr = "5D5cwd8DX6ij7nouVcoxDuWtJfiR1BnzCkiBVTt7DU8ft5Ta" + seed_str = "0x659c024d5be809000d0d93fe378cfde020846150b01c49a201fc2a02041f7636" + with patch.object(self.mock_wallet, "set_coldkey") as mock_set_coldkey: + self.mock_wallet.regenerate_coldkey( + seed=seed_str, overwrite=True, suppress=True + ) + + mock_set_coldkey.assert_called_once() + keypair: bittensor.Keypair = mock_set_coldkey.call_args_list[0][0][0] + self.assertRegex( + keypair.seed_hex + if isinstance(keypair.seed_hex, str) + else keypair.seed_hex.hex(), + rf"(0x|){seed_str[2:]}", + ) + self.assertEqual( + keypair.ss58_address, ss58_addr + ) # Check that the ss58 address is correct + + seed_str_bad = "0x659c024d5be809000d0d93fe378cfde020846150b01c49a201fc2a02041f763" # 1 character short + with pytest.raises(ValueError): + self.mock_wallet.regenerate_coldkey( + seed=seed_str_bad, overwrite=True, suppress=True + ) + + def test_regen_hotkey_from_hex_seed_str(self): + """Test the `regenerate_coldkey` method of the wallet class, which regenerates the cold key pair from a hex seed string. + It checks whether the `set_coldkey` method is called with the expected arguments, and verifies that the generated key pair's seed and SS58 address match the input seed and the expected SS58 address. + It also tests the behavior when an invalid seed string is provided, raising a `ValueError` as expected. + """ + ss58_addr = "5D5cwd8DX6ij7nouVcoxDuWtJfiR1BnzCkiBVTt7DU8ft5Ta" + seed_str = "0x659c024d5be809000d0d93fe378cfde020846150b01c49a201fc2a02041f7636" + with patch.object(self.mock_wallet, "set_hotkey") as mock_set_hotkey: + self.mock_wallet.regenerate_hotkey( + seed=seed_str, overwrite=True, suppress=True + ) + + mock_set_hotkey.assert_called_once() + keypair: bittensor.Keypair = mock_set_hotkey.call_args_list[0][0][0] + self.assertRegex( + keypair.seed_hex + if isinstance(keypair.seed_hex, str) + else keypair.seed_hex.hex(), + rf"(0x|){seed_str[2:]}", + ) + self.assertEqual( + keypair.ss58_address, ss58_addr + ) # Check that the ss58 address is correct + + seed_str_bad = "0x659c024d5be809000d0d93fe378cfde020846150b01c49a201fc2a02041f763" # 1 character short + with pytest.raises(ValueError): + self.mock_wallet.regenerate_hotkey( + seed=seed_str_bad, overwrite=True, suppress=True + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/bittensor/_dendrite/__init__.py b/tests/unit_tests/utils/__init__.py similarity index 100% rename from bittensor/_dendrite/__init__.py rename to tests/unit_tests/utils/__init__.py diff --git a/tests/unit_tests/bittensor_tests/test_balance.py b/tests/unit_tests/utils/test_balance.py similarity index 85% rename from tests/unit_tests/bittensor_tests/test_balance.py rename to tests/unit_tests/utils/test_balance.py index 3d81921ab7..3b2ca26b70 100644 --- a/tests/unit_tests/bittensor_tests/test_balance.py +++ b/tests/unit_tests/utils/test_balance.py @@ -1,27 +1,12 @@ -# The MIT License (MIT) -# Copyright © 2022 Yuma Rao - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# 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 unittest -from typing import Union import pytest -from bittensor import Balance from hypothesis import given from hypothesis import strategies as st +from typing import Union + +from bittensor import Balance +from tests.helpers import CLOSE_IN_VALUE from tests.helpers import CLOSE_IN_VALUE @@ -48,6 +33,9 @@ def remove_zero_filter(x): class TestBalance(unittest.TestCase): @given(balance=valid_tao_numbers_strategy) def test_balance_init(self, balance: Union[int, float]): + """ + Test the initialization of the Balance object. + """ balance_ = Balance(balance) if isinstance(balance, int): assert balance_.rao == balance @@ -56,6 +44,9 @@ def test_balance_init(self, balance: Union[int, float]): @given(balance=valid_tao_numbers_strategy, balance2=valid_tao_numbers_strategy) def test_balance_add(self, balance: Union[int, float], balance2: Union[int, float]): + """ + Test the addition of two Balance objects. + """ balance_ = Balance(balance) balance2_ = Balance(balance2) rao_: int @@ -77,6 +68,9 @@ def test_balance_add(self, balance: Union[int, float], balance2: Union[int, floa def test_balance_add_other_not_balance( self, balance: Union[int, float], balance2: Union[int, float] ): + """ + Test the addition of a Balance object and a non-Balance object. + """ balance_ = Balance(balance) balance2_ = balance2 rao_: int @@ -94,6 +88,9 @@ def test_balance_add_other_not_balance( @given(balance=valid_tao_numbers_strategy) def test_balance_eq_other_not_balance(self, balance: Union[int, float]): + """ + Test the equality of a Balance object and a non-Balance object. + """ balance_ = Balance(balance) rao2_: int # convert balance2 to rao. This assumes balance2 is a rao value @@ -109,6 +106,9 @@ def test_balance_eq_other_not_balance(self, balance: Union[int, float]): def test_balance_radd_other_not_balance( self, balance: Union[int, float], balance2: Union[int, float] ): + """ + Test the right addition (radd) of a Balance object and a non-Balance object. + """ balance_ = Balance(balance) balance2_ = balance2 rao_: int @@ -126,6 +126,9 @@ def test_balance_radd_other_not_balance( @given(balance=valid_tao_numbers_strategy, balance2=valid_tao_numbers_strategy) def test_balance_sub(self, balance: Union[int, float], balance2: Union[int, float]): + """ + Test the subtraction of two Balance objects. + """ balance_ = Balance(balance) balance2_ = Balance(balance2) rao_: int @@ -147,6 +150,9 @@ def test_balance_sub(self, balance: Union[int, float], balance2: Union[int, floa def test_balance_sub_other_not_balance( self, balance: Union[int, float], balance2: Union[int, float] ): + """ + Test the subtraction of a Balance object and a non-Balance object. + """ balance_ = Balance(balance) balance2_ = balance2 rao_: int @@ -166,6 +172,9 @@ def test_balance_sub_other_not_balance( def test_balance_rsub_other_not_balance( self, balance: Union[int, float], balance2: Union[int, float] ): + """ + Test the right subtraction (rsub) of a Balance object and a non-Balance object. + """ balance_ = Balance(balance) balance2_ = balance2 rao_: int @@ -183,6 +192,9 @@ def test_balance_rsub_other_not_balance( @given(balance=valid_tao_numbers_strategy, balance2=valid_tao_numbers_strategy) def test_balance_mul(self, balance: Union[int, float], balance2: Union[int, float]): + """ + Test the multiplication of two Balance objects. + """ balance_ = Balance(balance) balance2_ = Balance(balance2) rao_: int @@ -210,6 +222,9 @@ def test_balance_mul(self, balance: Union[int, float], balance2: Union[int, floa def test_balance_mul_other_not_balance( self, balance: Union[int, float], balance2: Union[int, float] ): + """ + Test the multiplication of a Balance object and a non-Balance object. + """ balance_ = Balance(balance) balance2_ = balance2 rao_: int @@ -226,6 +241,9 @@ def test_balance_mul_other_not_balance( def test_balance_rmul_other_not_balance( self, balance: Union[int, float], balance2: Union[int, float] ): + """ + Test the right multiplication (rmul) of a Balance object and a non-Balance object. + """ balance_ = Balance(balance) balance2_ = balance2 rao_: int @@ -250,6 +268,9 @@ def test_balance_rmul_other_not_balance( def test_balance_truediv( self, balance: Union[int, float], balance2: Union[int, float] ): + """ + Test the true division (/) of two Balance objects. + """ balance_ = Balance(balance) balance2_ = Balance(balance2) rao_: int @@ -279,6 +300,9 @@ def test_balance_truediv( def test_balance_truediv_other_not_balance( self, balance: Union[int, float], balance2: Union[int, float] ): + """ + Test the true division (/) of a Balance object and a non-Balance object. + """ balance_ = Balance(balance) balance2_ = balance2 rao_: int @@ -307,6 +331,9 @@ def test_balance_truediv_other_not_balance( def test_balance_rtruediv_other_not_balance( self, balance: Union[int, float], balance2: Union[int, float] ): + """ + Test the right true division (rtruediv) of a Balance object and a non-Balance object. + """ balance_ = Balance(balance) balance2_ = balance2 rao_: int @@ -334,6 +361,9 @@ def test_balance_rtruediv_other_not_balance( def test_balance_floordiv( self, balance: Union[int, float], balance2: Union[int, float] ): + """ + Test the floor division (//) of two Balance objects. + """ balance_ = Balance(balance) balance2_ = Balance(balance2) rao_: int @@ -358,6 +388,9 @@ def test_balance_floordiv( def test_balance_floordiv_other_not_balance( self, balance: Union[int, float], balance2: Union[int, float] ): + """ + Test the floor division (//) of a Balance object and a non-Balance object. + """ balance_ = Balance(balance) balance2_ = balance2 rao_: int @@ -387,6 +420,9 @@ def test_balance_floordiv_other_not_balance( def test_balance_rfloordiv_other_not_balance( self, balance: Union[int, float], balance2: Union[int, float] ): + """ + Test the right floor division (rfloordiv) of a Balance object and a non-Balance object. + """ balance_ = Balance(balance) balance2_ = balance2 rao_: int @@ -404,48 +440,68 @@ def test_balance_rfloordiv_other_not_balance( @given(balance=valid_tao_numbers_strategy) def test_balance_not_eq_none(self, balance: Union[int, float]): + """ + Test the inequality (!=) of a Balance object and None. + """ balance_ = Balance(balance) assert not balance_ == None @given(balance=valid_tao_numbers_strategy) def test_balance_neq_none(self, balance: Union[int, float]): + """ + Test the inequality (!=) of a Balance object and None. + """ balance_ = Balance(balance) assert balance_ != None def test_balance_init_from_invalid_value(self): + """ + Test the initialization of a Balance object with an invalid value. + """ with pytest.raises(TypeError): Balance("invalid not a number") @given(balance=valid_tao_numbers_strategy) def test_balance_add_invalid_type(self, balance: Union[int, float]): + """ + Test the addition of a Balance object with an invalid type. + """ balance_ = Balance(balance) with pytest.raises(NotImplementedError): _ = balance_ + "" @given(balance=valid_tao_numbers_strategy) def test_balance_sub_invalid_type(self, balance: Union[int, float]): + """ + Test the subtraction of a Balance object with an invalid type. + """ balance_ = Balance(balance) with pytest.raises(NotImplementedError): _ = balance_ - "" @given(balance=valid_tao_numbers_strategy) def test_balance_div_invalid_type(self, balance: Union[int, float]): + """ + Test the division of a Balance object with an invalid type. + """ balance_ = Balance(balance) with pytest.raises(NotImplementedError): _ = balance_ / "" @given(balance=valid_tao_numbers_strategy) def test_balance_mul_invalid_type(self, balance: Union[int, float]): + """ + Test the multiplication of a Balance object with an invalid type. + """ balance_ = Balance(balance) with pytest.raises(NotImplementedError): _ = balance_ * "" @given(balance=valid_tao_numbers_strategy) def test_balance_eq_invalid_type(self, balance: Union[int, float]): + """ + Test the equality of a Balance object with an invalid type. + """ balance_ = Balance(balance) with pytest.raises(NotImplementedError): balance_ == "" - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/unit_tests/bittensor_tests/utils/test_network_utils.py b/tests/unit_tests/utils/test_networking.py similarity index 68% rename from tests/unit_tests/bittensor_tests/utils/test_network_utils.py rename to tests/unit_tests/utils/test_networking.py index 753bcd0a9c..8e729a8594 100644 --- a/tests/unit_tests/bittensor_tests/utils/test_network_utils.py +++ b/tests/unit_tests/utils/test_networking.py @@ -1,29 +1,32 @@ -from bittensor import utils -import unittest.mock as mock -from unittest.mock import MagicMock, PropertyMock import os -import requests import urllib import pytest +import requests +import unittest.mock as mock +from bittensor import utils +from unittest.mock import MagicMock, PropertyMock +# Test conversion functions for IPv4 def test_int_to_ip_zero(): + """Test converting integer to IPv4 address for 0.""" assert utils.networking.int_to_ip(0) == "0.0.0.0" assert utils.networking.ip_to_int("0.0.0.0") == 0 assert utils.networking.ip__str__(4, "0.0.0.0", 8888) == "/ipv4/0.0.0.0:8888" def test_int_to_ip_range(): + """Test converting integer to IPv4 addresses in a range.""" for i in range(10): - assert utils.networking.int_to_ip(i) == "0.0.0." + str(i) - assert utils.networking.ip_to_int("0.0.0." + str(i)) == i + assert utils.networking.int_to_ip(i) == f"0.0.0.{i}" + assert utils.networking.ip_to_int(f"0.0.0.{i}") == i assert ( - utils.networking.ip__str__(4, "0.0.0." + str(i), 8888) - == "/ipv4/0.0.0." + str(i) + ":8888" + utils.networking.ip__str__(4, f"0.0.0.{i}", 8888) == f"/ipv4/0.0.0.{i}:8888" ) def test_int_to_ip4_max(): + """Test converting integer to maximum IPv4 address.""" assert utils.networking.int_to_ip(4294967295) == "255.255.255.255" assert utils.networking.ip_to_int("255.255.255.255") == 4294967295 assert ( @@ -32,23 +35,26 @@ def test_int_to_ip4_max(): ) +# Test conversion functions for IPv6 def test_int_to_ip6_zero(): + """Test converting integer to IPv6 address for 0.""" assert utils.networking.int_to_ip(4294967296) == "::1:0:0" assert utils.networking.ip_to_int("::1:0:0") == 4294967296 assert utils.networking.ip__str__(6, "::1:0:0", 8888) == "/ipv6/::1:0:0:8888" def test_int_to_ip6_range(): + """Test converting integer to IPv6 addresses in a range.""" for i in range(10): - assert utils.networking.int_to_ip(4294967296 + i) == "::1:0:" + str(i) - assert utils.networking.ip_to_int("::1:0:" + str(i)) == 4294967296 + i + assert utils.networking.int_to_ip(4294967296 + i) == f"::1:0:{i}" + assert utils.networking.ip_to_int(f"::1:0:{i}") == 4294967296 + i assert ( - utils.networking.ip__str__(6, "::1:0:" + str(i), 8888) - == "/ipv6/::1:0:" + str(i) + ":8888" + utils.networking.ip__str__(6, f"::1:0:{i}", 8888) == f"/ipv6/::1:0:{i}:8888" ) def test_int_to_ip6_max(): + """Test converting integer to maximum IPv6 address.""" max_val = 340282366920938463463374607431768211455 assert ( utils.networking.int_to_ip(max_val) == "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" @@ -63,67 +69,68 @@ def test_int_to_ip6_max(): def test_int_to_ip6_overflow(): + """Test handling overflow when converting integer to IPv6 address.""" overflow = 340282366920938463463374607431768211455 + 1 - try: + with pytest.raises(Exception): utils.networking.int_to_ip(overflow) - except: - assert True def test_int_to_ip6_underflow(): + """Test handling underflow when converting integer to IPv6 address.""" underflow = -1 - try: + with pytest.raises(Exception): utils.networking.int_to_ip(underflow) - except: - assert True +# Test getting external IP address def test_get_external_ip(): + """Test getting the external IP address.""" assert utils.networking.get_external_ip() def test_get_external_ip_os_broken(): - class fake: + """Test getting the external IP address when os.popen is broken.""" + + class FakeReadline: def readline(self): return 1 def mock_call(): - return fake() + return FakeReadline() with mock.patch.object(os, "popen", new=mock_call): assert utils.networking.get_external_ip() def test_get_external_ip_os_request_urllib_broken(): - class fake: + """Test getting the external IP address when os.popen and requests.get/urllib.request are broken.""" + + class FakeReadline: def readline(self): return 1 def mock_call(): - return fake() + return FakeReadline() - class fake_s: + class FakeResponse: def text(self): return 1 def mock_call_two(): - return fake_s() + return FakeResponse() - class fake_a: + class FakeRequest: def urlopen(self): return 1 with mock.patch.object(os, "popen", new=mock_call): with mock.patch.object(requests, "get", new=mock_call_two): - urllib.request = MagicMock(return_value=fake_a()) + urllib.request = MagicMock(return_value=FakeRequest()) with pytest.raises(Exception): assert utils.networking.get_external_ip() -def returnNoPortMapping(): - return None - - +# Test formatting WebSocket endpoint URL @pytest.mark.parametrize( "url, expected", [ @@ -156,8 +163,5 @@ def returnNoPortMapping(): ], ) def test_format(url: str, expected: str): + """Test formatting WebSocket endpoint URL.""" assert utils.networking.get_formatted_ws_endpoint_url(url) == expected - - -if __name__ == "__main__": - test_get_external_ip() diff --git a/tests/unit_tests/bittensor_tests/utils/test_utils.py b/tests/unit_tests/utils/test_utils.py similarity index 84% rename from tests/unit_tests/bittensor_tests/utils/test_utils.py rename to tests/unit_tests/utils/test_utils.py index 5c908f0b00..307891a13c 100644 --- a/tests/unit_tests/bittensor_tests/utils/test_utils.py +++ b/tests/unit_tests/utils/test_utils.py @@ -1,4 +1,21 @@ -import binascii +# The MIT License (MIT) +# Copyright © 2022 Opentensor Foundation +# Copyright © 2023 Opentensor Technologies Inc + +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the “Software”), to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of +# the Software. + +# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# 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 hashlib import math import multiprocessing @@ -23,85 +40,15 @@ from substrateinterface.base import Keypair import bittensor +from bittensor.mock import MockSubtensor from bittensor.utils.registration import _CUDASolver, _SolverBase -from bittensor._subtensor.subtensor_mock import MockSubtensor from tests.helpers import _get_mock_wallet as _generate_wallet, _get_mock_keypair -@fixture(scope="function") -def setup_chain(): - operating_system = "OSX" if platform == "darwin" else "Linux" - path = "./bin/chain/{}/node-subtensor".format(operating_system) - logger.info(path) - if not path: - logger.error( - "make sure the NODE_SUBTENSOR_BIN env var is set and points to the node-subtensor binary" - ) - sys.exit() - - # Select a port - port = select_port() - - # Delete existing wallets - # subprocess.Popen(["rm", '-r', '~/.bittensor/wallets/*testwallet'], close_fds=True, shell=False) - - # Purge chain first - subprocess.Popen([path, "purge-chain", "--dev", "-y"], close_fds=True, shell=False) - proc = subprocess.Popen( - [ - path, - "--dev", - "--port", - str(port + 1), - "--ws-port", - str(port), - "--rpc-port", - str(port + 2), - "--tmp", - ], - close_fds=True, - shell=False, - ) - - # Wait 4 seconds for the node to come up - time.sleep(4) - - yield port - - # Wait 4 seconds for the node to come up - time.sleep(4) - - # Kill process - os.system("kill %i" % proc.pid) - - -@pytest.fixture(scope="session", autouse=True) -def initialize_tests(): - # Kill any running process before running tests - os.system("pkill node-subtensor") - - -def select_port(): - port = random.randrange(1000, 65536, 5) - return port - - -def setup_subtensor(port: int): - chain_endpoint = "localhost:{}".format(port) - subtensor = bittensor.subtensor( - chain_endpoint=chain_endpoint, - ) - return subtensor, port - - def construct_config(): - defaults = bittensor.Config() - bittensor.subtensor.add_defaults(defaults) - bittensor.dendrite.add_defaults(defaults) - bittensor.axon.add_defaults(defaults) - bittensor.wallet.add_defaults(defaults) - bittensor.dataset.add_defaults(defaults) + parser = bittensor.cli.__create_parser__() + defaults = bittensor.config(parser=parser, args=[]) return defaults @@ -178,7 +125,7 @@ def test_solve_for_difficulty_fast(self): subtensor.difficulty = MagicMock(return_value=10) solution = bittensor.utils.registration._solve_for_difficulty_fast( - subtensor, wallet, netuid=-1, num_processes=num_proc + subtensor, wallet, netuid=-2, num_processes=num_proc ) seal = solution.seal assert bittensor.utils.registration._seal_meets_difficulty(seal, 10, limit) @@ -200,7 +147,7 @@ def test_solve_for_difficulty_fast_registered_already(self): subtensor = MagicMock() subtensor.get_current_block = MagicMock(return_value=1) subtensor.difficulty = MagicMock( - return_value=int(1e10) + return_value=int(1e20) ) # set high to make solving take a long time subtensor.substrate = MagicMock() subtensor.get_block_hash = MagicMock(return_value=block_hash) @@ -213,7 +160,7 @@ def test_solve_for_difficulty_fast_registered_already(self): # all arugments should return None to indicate an early return solution = bittensor.utils.registration._solve_for_difficulty_fast( - subtensor, wallet, netuid=-1, num_processes=1, update_interval=1000 + subtensor, wallet, netuid=-2, num_processes=1, update_interval=1000 ) assert solution is None @@ -248,7 +195,7 @@ def test_solve_for_difficulty_fast_missing_hash(self): assert bittensor.utils.registration._seal_meets_difficulty(seal, 1, limit) subtensor.difficulty = MagicMock(return_value=10) solution = bittensor.utils.registration._solve_for_difficulty_fast( - subtensor, wallet, netuid=-1, num_processes=num_proc + subtensor, wallet, netuid=-2, num_processes=num_proc ) seal = solution.seal assert bittensor.utils.registration._seal_meets_difficulty(seal, 10, limit) @@ -481,7 +428,7 @@ def test_check_for_newest_block_and_update_new_block(self): self.assertEqual( bittensor.utils.registration._check_for_newest_block_and_update( subtensor, - -1, # netuid + -2, # netuid MagicMock(), mock_hotkey_bytes, MagicMock(), @@ -566,7 +513,9 @@ def block_none_twice(block_hash: bytes): class TestPOWNotStale(unittest.TestCase): def test_pow_not_stale_same_block_number(self): - mock_subtensor = MagicMock(get_current_block=MagicMock(return_value=1)) + mock_subtensor = MagicMock( + get_current_block=MagicMock(return_value=1), + ) mock_solution = bittensor.utils.registration.POWSolution( block_number=1, # 3 less than current block number nonce=1, @@ -577,7 +526,9 @@ def test_pow_not_stale_same_block_number(self): assert not mock_solution.is_stale(mock_subtensor) def test_pow_not_stale_diff_block_number(self): - mock_subtensor = MagicMock(get_current_block=MagicMock(return_value=2)) + mock_subtensor = MagicMock( + get_current_block=MagicMock(return_value=2), + ) mock_solution = bittensor.utils.registration.POWSolution( block_number=1, # 1 less than current block number nonce=1, @@ -587,7 +538,9 @@ def test_pow_not_stale_diff_block_number(self): assert not mock_solution.is_stale(mock_subtensor) - mock_subtensor = MagicMock(get_current_block=MagicMock(return_value=3)) + mock_subtensor = MagicMock( + get_current_block=MagicMock(return_value=3), + ) mock_solution = bittensor.utils.registration.POWSolution( block_number=1, # 2 less than current block number nonce=1, @@ -597,7 +550,9 @@ def test_pow_not_stale_diff_block_number(self): assert not mock_solution.is_stale(mock_subtensor) - mock_subtensor = MagicMock(get_current_block=MagicMock(return_value=4)) + mock_subtensor = MagicMock( + get_current_block=MagicMock(return_value=4), + ) mock_solution = bittensor.utils.registration.POWSolution( block_number=1, # 3 less than current block number nonce=1, @@ -608,7 +563,9 @@ def test_pow_not_stale_diff_block_number(self): assert not mock_solution.is_stale(mock_subtensor) def test_pow_not_stale_diff_block_number_too_old(self): - mock_subtensor = MagicMock(get_current_block=MagicMock(return_value=5)) + mock_subtensor = MagicMock( + get_current_block=MagicMock(return_value=5), + ) mock_solution = bittensor.utils.registration.POWSolution( block_number=1, # 4 less than current block number nonce=1, @@ -619,20 +576,23 @@ def test_pow_not_stale_diff_block_number_too_old(self): assert mock_solution.is_stale(mock_subtensor) +@patch("torch.cuda.is_available", return_value=True) class TestPOWCalled(unittest.TestCase): def setUp(self) -> None: # Setup mock subnet - self._subtensor = bittensor.subtensor(_mock=True) - + self._subtensor = MockSubtensor() + self._subtensor.reset() self._subtensor.create_subnet(netuid=99) - def test_pow_called_for_cuda(self): + def test_pow_called_for_cuda(self, mock_cuda_available): class MockException(Exception): pass mock_pow_register_call = MagicMock(side_effect=MockException) - mock_subtensor = bittensor.subtensor(_mock=True) + mock_subtensor = MockSubtensor() + mock_subtensor.reset() + mock_subtensor.create_subnet(netuid=99) mock_subtensor.get_neuron_for_pubkey_and_subnet = MagicMock(is_null=True) mock_subtensor._do_pow_register = mock_pow_register_call @@ -654,28 +614,24 @@ class MockException(Exception): is_stale=mock_pow_is_stale, ) - with patch("torch.cuda.is_available", return_value=True) as mock_cuda_available: - with patch( - "bittensor._subtensor.extrinsics.registration.create_pow", - return_value=mock_result, - ) as mock_create_pow: - # Should exit early - with pytest.raises(MockException): - mock_subtensor.register( - mock_wallet, netuid=99, cuda=True, prompt=False - ) + with patch( + "bittensor.extrinsics.registration.create_pow", return_value=mock_result + ) as mock_create_pow: + # Should exit early + with pytest.raises(MockException): + mock_subtensor.register(mock_wallet, netuid=99, cuda=True, prompt=False) - mock_pow_is_stale.assert_called_once() - mock_create_pow.assert_called_once() - mock_cuda_available.assert_called_once() + mock_pow_is_stale.assert_called_once() + mock_create_pow.assert_called_once() + mock_cuda_available.assert_called_once() - call0 = mock_pow_is_stale.call_args - _, kwargs = call0 - assert kwargs["subtensor"] == mock_subtensor + call0 = mock_pow_is_stale.call_args + _, kwargs = call0 + assert kwargs["subtensor"] == mock_subtensor - mock_pow_register_call.assert_called_once() - _, kwargs = mock_pow_register_call.call_args - kwargs["pow_result"].nonce == mock_result.nonce + mock_pow_register_call.assert_called_once() + _, kwargs = mock_pow_register_call.call_args + kwargs["pow_result"].nonce == mock_result.nonce class TestCUDASolverRun(unittest.TestCase): @@ -810,12 +766,12 @@ class TestWalletReregister(unittest.TestCase): _mock_subtensor: MockSubtensor def setUp(self): - self.subtensor = bittensor.subtensor(network="mock") # own instance per test + self.subtensor = MockSubtensor() # own instance per test @classmethod def setUpClass(cls) -> None: # Keeps the same mock network for all tests. This stops the network from being re-setup for each test. - cls._mock_subtensor = bittensor.subtensor(network="mock") + cls._mock_subtensor = MockSubtensor() cls._do_setup_subnet() @@ -832,10 +788,13 @@ def test_wallet_reregister_reregister_false(self): class MockException(Exception): pass - with patch( - "bittensor._subtensor.extrinsics.registration.register_extrinsic", - side_effect=MockException, - ) as mock_register: + # Determine the correct string for patch based on Python version + if sys.version_info >= (3, 11): + patch_string = "bittensor.subtensor.subtensor.register" + else: + patch_string = "bittensor.subtensor.register" + + with patch(patch_string, side_effect=MockException) as mock_register: with pytest.raises(SystemExit): # should exit because it's not registered bittensor.utils.reregister( wallet=mock_wallet, @@ -864,10 +823,13 @@ class MockException(Exception): ) ) - with patch( - "bittensor._subtensor.subtensor_impl.register_extrinsic", - side_effect=MockException, - ) as mock_register: + # Determine the correct string for patch based on Python version + if sys.version_info >= (3, 11): + patch_string = "bittensor.subtensor.subtensor.register" + else: + patch_string = "bittensor.subtensor.register" + + with patch(patch_string, side_effect=MockException) as mock_register: bittensor.utils.reregister( wallet=mock_wallet, subtensor=self._mock_subtensor, @@ -895,10 +857,13 @@ class MockException(Exception): ) ) - with patch( - "bittensor._subtensor.subtensor_impl.register_extrinsic", - side_effect=MockException, - ) as mock_register: + # Determine the correct string for patch based on Python version + if sys.version_info >= (3, 11): + patch_string = "bittensor.subtensor.subtensor.register" + else: + patch_string = "bittensor.subtensor.register" + + with patch(patch_string, side_effect=MockException) as mock_register: bittensor.utils.reregister( wallet=mock_wallet, subtensor=self._mock_subtensor, @@ -914,10 +879,13 @@ def test_wallet_reregister_no_params(self): class MockException(Exception): pass - with patch( - "bittensor._subtensor.subtensor_impl.register_extrinsic", - side_effect=MockException, - ) as mock_register: + # Determine the correct string for patch based on Python version + if sys.version_info >= (3, 11): + patch_string = "bittensor.subtensor.subtensor.register" + else: + patch_string = "bittensor.subtensor.register" + + with patch(patch_string, side_effect=MockException) as mock_register: # Should be able to set without argument with pytest.raises(MockException): bittensor.utils.reregister( @@ -936,27 +904,28 @@ def test_wallet_reregister_use_cuda_flag_true(self): class MockException(Exception): pass - with patch( - "bittensor._subtensor.subtensor_impl.register_extrinsic", - side_effect=MockException, - ) as mock_register: - # Should be able to set without argument - with pytest.raises(MockException): - bittensor.utils.reregister( - wallet=mock_wallet, - subtensor=self._mock_subtensor, - netuid=3, - dev_id=0, - cuda=True, - reregister=True, - ) + with patch("torch.cuda.is_available", return_value=True) as mock_cuda_available: + with patch( + "bittensor.extrinsics.registration.create_pow", + side_effect=MockException, + ) as mock_create_pow: + # Should be able to set without argument + with pytest.raises(MockException): + bittensor.utils.reregister( + wallet=mock_wallet, + subtensor=self._mock_subtensor, + netuid=3, + dev_id=0, + cuda=True, + reregister=True, + ) - call_args = mock_register.call_args - _, kwargs = call_args + call_args = mock_create_pow.call_args + _, kwargs = call_args - mock_register.assert_called_once() - self.assertIn("cuda", kwargs) - self.assertEqual(kwargs["cuda"], True) + mock_create_pow.assert_called_once() + self.assertIn("cuda", kwargs) + self.assertEqual(kwargs["cuda"], True) def test_wallet_reregister_use_cuda_flag_false(self): mock_wallet = _generate_wallet(hotkey=_get_mock_keypair(100, self.id())) @@ -965,9 +934,8 @@ class MockException(Exception): pass with patch( - "bittensor._subtensor.subtensor_impl.register_extrinsic", - side_effect=MockException, - ) as mock_register: + "bittensor.extrinsics.registration.create_pow", side_effect=MockException + ) as mock_create_pow: # Should be able to set without argument with pytest.raises(MockException): bittensor.utils.reregister( @@ -979,10 +947,10 @@ class MockException(Exception): reregister=True, ) - call_args = mock_register.call_args + call_args = mock_create_pow.call_args _, kwargs = call_args - mock_register.assert_called_once() + mock_create_pow.assert_called_once() self.assertEqual(kwargs["cuda"], False) def test_wallet_reregister_cuda_arg_not_specified_should_be_false(self): @@ -992,9 +960,8 @@ class MockException(Exception): pass with patch( - "bittensor._subtensor.subtensor_impl.register_extrinsic", - side_effect=MockException, - ) as mock_register: + "bittensor.extrinsics.registration.create_pow", side_effect=MockException + ) as mock_create_pow: # Should be able to set without argument with pytest.raises(MockException): bittensor.utils.reregister( @@ -1005,10 +972,10 @@ class MockException(Exception): reregister=True, ) - call_args = mock_register.call_args + call_args = mock_create_pow.call_args _, kwargs = call_args - mock_register.assert_called_once() + mock_create_pow.assert_called_once() self.assertEqual(kwargs["cuda"], False) # should be False by default diff --git a/tests/unit_tests/bittensor_tests/utils/test_weight_utils.py b/tests/unit_tests/utils/test_weight_utils.py similarity index 70% rename from tests/unit_tests/bittensor_tests/utils/test_weight_utils.py rename to tests/unit_tests/utils/test_weight_utils.py index af68c09878..c1530a8d69 100644 --- a/tests/unit_tests/bittensor_tests/utils/test_weight_utils.py +++ b/tests/unit_tests/utils/test_weight_utils.py @@ -1,7 +1,25 @@ +# The MIT License (MIT) +# Copyright © 2021 Yuma Rao +# Copyright © 2022 Opentensor Foundation +# Copyright © 2023 Opentensor Technologies Inc + +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the “Software”), to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of +# the Software. + +# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# 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.utils.weight_utils as weight_utils import pytest -import random def test_convert_weight_and_uids():