Skip to content
This repository was archived by the owner on Sep 18, 2024. It is now read-only.

Adds a cloudflare miner for the sprout subnet on bittensor #39

Merged
merged 1 commit into from
Apr 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -172,3 +172,7 @@ dist
# docker

.dockerignore


.env.development
.env.test
2 changes: 2 additions & 0 deletions packages/miner-cloudflare/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
bittensor_subnet_template.egg-info
__pycache__
111 changes: 111 additions & 0 deletions packages/miner-cloudflare/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# Sprout Compute Subnet

The Sprout Compute Subnet is a market place incentivizing compute resources to the most in demand open source AI models.

## Getting started

## Development Requirements:

You will need to setup and install a few tools to work on the subnet code.

1. [Install bittensor CLI](https://github.com/opentensor/bittensor#install)

TODO: review step 2 will full isntruction to download and install

2. Create wallets for the subnet owner, a validator and a miner

You will need wallets for the different roles, i.e., subnet owner, subnet validator and subnet miner, in the subnet.

The owner wallet creates and controls the subnet.
The validator and miner will be registered to the subnet created by the owner. This ensures that the validator and miner can run the respective validator and miner scripts.
Create a coldkey for the owner role:

```
btcli wallet new_coldkey --wallet.name owner
```

Set up the miner's wallets:

```
btcli wallet new_coldkey --wallet.name miner
btcli wallet new_hotkey --wallet.name miner --wallet.hotkey default
```

Set up the validator's wallets:

```
btcli wallet new_coldkey --wallet.name validator
btcli wallet new_hotkey --wallet.name validator --wallet.hotkey default
```

## Start a development environment

after having installed the requirements and creating wallets, we'll start the network and create the subnet. These steps will be repeated each time you restart your development environment:

1. From the subtensor repo start a test chain

```
BUILD_BINARY=0 ./scripts/localnet.sh
```

2. Create a [`.env`](./scripts/.env) file in `/scripts`, the content can be shown from [`./scripts/.env.example`](./scripts/.env.example), replace with your wallets password

3. install dependencies:

```
python -m pip install -e .
```

4. Mint Tokens for each wallet. The mint script will give a total of 300 test TAO each run

The owner needs a total of 1,000,000 Tao tokens. Run the script 4 times

```
python scripts/faucet.py --wallet owner
```

The validator needs only 1 run of the script

```
python scripts/faucet.py --wallet validator
```

same thing for the miner

```
python scripts/faucet.py --wallet miner
```

5. create the subnet:

```
btcli subnet create --wallet.name owner --subtensor.chain_endpoint ws://127.0.0.1:9946
```

6. Register the miner and validator:

```
btcli subnet register --wallet.name miner --wallet.hotkey default --subtensor.chain_endpoint ws://127.0.0.1:9946
```

```
btcli subnet register --wallet.name validator --wallet.hotkey default --subtensor.chain_endpoint ws://127.0.0.1:9946
```

7. Add a stake to the subnet:

```
btcli stake add --wallet.name validator --wallet.hotkey default --subtensor.chain_endpoint ws://127.0.0.1:9946
```

8. Start the miner:

```
python neurons/miner/miner.py --netuid 1 --subtensor.chain_endpoint ws://127.0.0.1:9946 --wallet.name miner --wallet.hotkey default --logging.debug
```

9. Start the validator:

```
python neurons/validator.py --netuid 1 --subtensor.chain_endpoint ws://127.0.0.1:9946 --wallet.name validator --wallet.hotkey default --logging.debug
```
7 changes: 7 additions & 0 deletions packages/miner-cloudflare/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
__version__ = "0.0.0"
version_split = __version__.split(".")
__spec_version__ = (
(1000 * int(version_split[0]))
+ (10 * int(version_split[1]))
+ (1 * int(version_split[2]))
)
116 changes: 116 additions & 0 deletions packages/miner-cloudflare/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import bittensor as bt
import argparse
import os


def check_config(cls, config: "bt.Config"):
bt.axon.check_config(config)
bt.logging.check_config(config)
full_path = os.path.expanduser(
"{}/{}/{}/{}".format(
config.logging.logging_dir,
config.wallet.get("name", bt.defaults.wallet.name),
config.wallet.get("hotkey", bt.defaults.wallet.hotkey),
config.miner.name,
)
)
config.miner.full_path = os.path.expanduser(full_path)
if not os.path.exists(config.miner.full_path):
os.makedirs(config.miner.full_path)


def get_config() -> "bt.Config":
parser = argparse.ArgumentParser()
parser.add_argument(
"--axon.port", type=int, default=8098, help="Port to run the axon on."
)
# Subtensor network to connect to
parser.add_argument(
"--subtensor.network",
default="finney",
help="Bittensor network to connect to.",
)
# Chain endpoint to connect to
parser.add_argument(
"--subtensor.chain_endpoint",
default="wss://entrypoint-finney.opentensor.ai:443",
help="Chain endpoint to connect to.",
)
# Adds override arguments for network and netuid.
parser.add_argument(
"--netuid", type=int, default=1, help="The chain subnet uid."
)

parser.add_argument(
"--miner.root",
type=str,
help="Trials for this miner go in miner.root / (wallet_cold - wallet_hot) / miner.name ",
default="~/.bittensor/miners/",
)
parser.add_argument(
"--miner.name",
type=str,
help="Trials for this miner go in miner.root / (wallet_cold - wallet_hot) / miner.name ",
default="Bittensor Miner",
)

# Run config.
parser.add_argument(
"--miner.blocks_per_epoch",
type=str,
help="Blocks until the miner repulls the metagraph from the chain",
default=100,
)

# Switches.
parser.add_argument(
"--miner.no_serve",
action="store_true",
help="If True, the miner doesnt serve the axon.",
default=False,
)
parser.add_argument(
"--miner.no_start_axon",
action="store_true",
help="If True, the miner doesnt start the axon.",
default=False,
)

# Mocks.
parser.add_argument(
"--miner.mock_subtensor",
action="store_true",
help="If True, the miner will allow non-registered hotkeys to mine.",
default=False,
)

# Adds subtensor specific arguments i.e. --subtensor.chain_endpoint ... --subtensor.network ...
bt.subtensor.add_args(parser)

# Adds logging specific arguments i.e. --logging.debug ..., --logging.trace .. or --logging.logging_dir ...
bt.logging.add_args(parser)

# Adds wallet specific arguments i.e. --wallet.name ..., --wallet.hotkey ./. or --wallet.path ...
bt.wallet.add_args(parser)

# Adds axon specific arguments i.e. --axon.port ...
bt.axon.add_args(parser)

# Activating the parser to read any command-line inputs.
# To print help message, run python3 template/miner.py --help
config = bt.config(parser)

# Logging captures events for diagnosis or understanding miner's behavior.
config.full_path = os.path.expanduser(
"{}/{}/{}/netuid{}/{}".format(
config.logging.logging_dir,
config.wallet.name,
config.wallet.hotkey,
config.netuid,
"miner",
)
)
# Ensure the directory for logging exists, else create one.
if not os.path.exists(config.full_path):
os.makedirs(config.full_path, exist_ok=True)
return config
38 changes: 38 additions & 0 deletions packages/miner-cloudflare/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import time
import argparse
import bittensor as bt
from protocol import StreamPrompting
import requests

from stream_miner import StreamMiner


class Miner(StreamMiner):
def config(self) -> "bt.Config":

parser = argparse.ArgumentParser(description="Streaming Miner Configs")
self.add_args(parser)
return bt.config(parser)

def add_args(cls, parser: argparse.ArgumentParser):
pass

def prompt(self, synapse: StreamPrompting) -> StreamPrompting:
response = requests.post(
f"https://api.cloudflare.com/client/v4/accounts/{self.CLOUDFLARE_ACCOUNT_ID}/ai/run/@cf/meta/{synapse.model}",
headers={"Authorization": f"Bearer {self.CLOUDFLARE_AUTH_TOKEN}"},
json={
"messages": synapse.messages
}
)
json_resp = response.json()

synapse.completion = json_resp['result']['response']
return synapse


# This is the main function, which runs the miner.
if __name__ == "__main__":
with Miner():
while True:
time.sleep(1)
8 changes: 8 additions & 0 deletions packages/miner-cloudflare/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "miner-cloudflare",
"description": "A python application allowing mining on the sprout subnet. This miner is for users willing to give access to their cloudflare models instead of hosting them directly",
"scripts": {
"bootstrap": "python -m pip install -e .",
"dev:local": "python main.py --netuid 1 --subtensor.chain_endpoint ws://127.0.0.1:9946 --wallet.name miner --wallet.hotkey default --logging.debug"
}
}
85 changes: 85 additions & 0 deletions packages/miner-cloudflare/protocol.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import pydantic
import bittensor as bt

from typing import List
from starlette.responses import StreamingResponse


class StreamPrompting(bt.StreamingSynapse):
messages: List[dict] = pydantic.Field(
[
{"role": "system", "content": "You are a friendly assistant"},
{"role": "user", "content": "hello this is a test of a streaming response. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."}
],
title="Messages",
description="A list of messages in the StreamPrompting scenario. Immutable.",
allow_mutation=False,
)

required_hash_fields: List[str] = pydantic.Field(
["messages"],
title="Required Hash Fields",
description="A list of required fields for the hash.",
allow_mutation=False,
)

completion: str = pydantic.Field(
"",
title="Completion",
description="Completion status of the current StreamPrompting object. This attribute is mutable and can be updated.",
)

model: str = pydantic.Field(
"llama-2-7b-chat-int8",
title="Model",
description="The model to use for StreamPrompting. Currently, only 'llama-2-7b-chat-int8' is supported.",
)

async def process_streaming_response(self, response: StreamingResponse):
"""
... (method docstring remains the same)
"""
if self.completion is None:
self.completion = ""
bt.logging.debug(
"Processing streaming response (StreamingSynapse base class)."
)
async for chunk in response.content.iter_any():
tokens = chunk.decode("utf-8").split("\n")
for token in tokens:
if token:
self.completion += token
yield tokens

def deserialize(self) -> str:
"""
... (method docstring remains the same)
"""
return self.completion

def extract_response_json(self, response: StreamingResponse) -> dict:
"""
... (method docstring remains the same)
"""
headers = {
k.decode("utf-8"): v.decode("utf-8")
for k, v in response.__dict__["_raw_headers"]
}

def extract_info(prefix):
return {
key.split("_")[-1]: value
for key, value in headers.items()
if key.startswith(prefix)
}

return {
"name": headers.get("name", ""),
"timeout": float(headers.get("timeout", 0)),
"total_size": int(headers.get("total_size", 0)),
"header_size": int(headers.get("header_size", 0)),
"dendrite": extract_info("bt_header_dendrite"),
"axon": extract_info("bt_header_axon"),
"prompts": self.prompts,
"completion": self.completion,
}
6 changes: 6 additions & 0 deletions packages/miner-cloudflare/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
bittensor
torch
flask
pydantic
python-dotenv
simplejson
Loading
Loading