From 86210b54d582497fcdd6ab50e90caf9c6bd9ef10 Mon Sep 17 00:00:00 2001 From: Gui Bibeau Date: Mon, 3 Jun 2024 15:27:19 +0100 Subject: [PATCH] feat: scrub subnet mentions (#131) --- .../PULL_REQUEST_TEMPLATE.md | 3 +- README.md | 14 +- docs/contributing.md | 9 +- docs/issue_and_pr_template.md | 9 +- subnet/miner-cloudflare/.gitignore | 2 - subnet/miner-cloudflare/README.md | 21 -- subnet/miner-cloudflare/__init__.py | 7 - subnet/miner-cloudflare/api/__init__.py | 0 subnet/miner-cloudflare/api/api.py | 33 --- .../certification/__init__.py | 0 .../certification/certification_manager.py | 65 ---- subnet/miner-cloudflare/certification/hash.py | 14 - subnet/miner-cloudflare/config.py | 123 -------- subnet/miner-cloudflare/dockerfile | 10 - subnet/miner-cloudflare/main.py | 11 - subnet/miner-cloudflare/miner/__init__.py | 0 subnet/miner-cloudflare/miner/miner.py | 33 --- subnet/miner-cloudflare/miner/stream_miner.py | 278 ------------------ subnet/miner-cloudflare/protocol.py | 59 ---- subnet/miner-cloudflare/requirements.txt | 9 - subnet/miner-cloudflare/setup.py | 82 ------ subnet/validator/.gitignore | 2 - subnet/validator/Dockerfile | 12 - subnet/validator/README.md | 21 -- subnet/validator/__init__.py | 7 - subnet/validator/main.py | 92 ------ subnet/validator/miner_manager.py | 158 ---------- subnet/validator/mock.py | 122 -------- subnet/validator/neuron.py | 169 ----------- subnet/validator/protocol.py | 85 ------ subnet/validator/requirements.txt | 10 - subnet/validator/reward.py | 76 ----- subnet/validator/setup.py | 82 ------ subnet/validator/test.py | 21 -- subnet/validator/uids.py | 64 ---- subnet/validator/utils/__init__.py | 3 - subnet/validator/utils/config.py | 210 ------------- subnet/validator/utils/misc.py | 112 ------- subnet/validator/utils/uids.py | 64 ---- subnet/validator/validator.py | 229 --------------- 40 files changed, 11 insertions(+), 2310 deletions(-) delete mode 100644 subnet/miner-cloudflare/.gitignore delete mode 100644 subnet/miner-cloudflare/README.md delete mode 100644 subnet/miner-cloudflare/__init__.py delete mode 100644 subnet/miner-cloudflare/api/__init__.py delete mode 100644 subnet/miner-cloudflare/api/api.py delete mode 100644 subnet/miner-cloudflare/certification/__init__.py delete mode 100644 subnet/miner-cloudflare/certification/certification_manager.py delete mode 100644 subnet/miner-cloudflare/certification/hash.py delete mode 100644 subnet/miner-cloudflare/config.py delete mode 100644 subnet/miner-cloudflare/dockerfile delete mode 100644 subnet/miner-cloudflare/main.py delete mode 100644 subnet/miner-cloudflare/miner/__init__.py delete mode 100644 subnet/miner-cloudflare/miner/miner.py delete mode 100644 subnet/miner-cloudflare/miner/stream_miner.py delete mode 100644 subnet/miner-cloudflare/protocol.py delete mode 100644 subnet/miner-cloudflare/requirements.txt delete mode 100644 subnet/miner-cloudflare/setup.py delete mode 100644 subnet/validator/.gitignore delete mode 100644 subnet/validator/Dockerfile delete mode 100644 subnet/validator/README.md delete mode 100644 subnet/validator/__init__.py delete mode 100644 subnet/validator/main.py delete mode 100644 subnet/validator/miner_manager.py delete mode 100644 subnet/validator/mock.py delete mode 100644 subnet/validator/neuron.py delete mode 100644 subnet/validator/protocol.py delete mode 100644 subnet/validator/requirements.txt delete mode 100644 subnet/validator/reward.py delete mode 100644 subnet/validator/setup.py delete mode 100644 subnet/validator/test.py delete mode 100644 subnet/validator/uids.py delete mode 100644 subnet/validator/utils/__init__.py delete mode 100644 subnet/validator/utils/config.py delete mode 100644 subnet/validator/utils/misc.py delete mode 100644 subnet/validator/utils/uids.py delete mode 100644 subnet/validator/validator.py diff --git a/.github/PULL_REQUEST_TEMPLATE/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE/PULL_REQUEST_TEMPLATE.md index bba5b96..81aae6b 100644 --- a/.github/PULL_REQUEST_TEMPLATE/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE/PULL_REQUEST_TEMPLATE.md @@ -30,13 +30,12 @@ Briefly describe the purpose of the pull request and what it aims to achieve. In ### Description -This pull request updates the README to provide a clearer and more comprehensive understanding of Akeru.ai. The enhancements include detailed descriptions of new API features, the Bittensor Subnet integration, and improved section formatting. These updates aim to make the repository more welcoming and informative for new contributors and users. +This pull request updates the README to provide a clearer and more comprehensive understanding of Akeru.ai. The enhancements include detailed descriptions of new API features, and improved section formatting. These updates aim to make the repository more welcoming and informative for new contributors and users. ### Key Changes - **Expanded Introduction**: Better explains Akeru.ai's unique features and its differentiation in the AI space. - **Detailed API Features**: Includes use cases and development status for upcoming features. -- **Clarified Bittensor Subnet Integration**: Details on how the Bittensor Subnet enhances Akeru.ai’s capabilities, including security features and decentralized benefits. - **Preliminary Self-Hosting Info**: Provides initial guidelines on self-hosting and the requirements for validators and miners. ### Link to Issues diff --git a/README.md b/README.md index b51874c..969c639 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Akeru.ai -**Akeru.ai** is an open-source AI platform built on the cutting edge of decentralization. Leveraging the power of the Akeru AI edge network running on a Bittensor Subnet, Akeru.ai offers transparent, safe, and highly available AI capabilities. +Akeru.ai is an API platform that offers transparent, safe, and highly available AI capabilities. We strive to maintain a welcoming and inclusive community. ## What Sets Akeru.ai Apart? @@ -29,17 +29,6 @@ The Akeru API, currently under development, aims for full compatibility with Ope The Akeru API is ideal for creating AI agents and enhancing web and mobile applications with advanced AI capabilities. (Rate limits and costs are TBD). -## Bittensor Subnet Design - -Akeru.ai's mission is to democratize AI technology. Running on a fleet of Bittensor validators and miners, our subnet is open to all, promoting an equitable AI future. - -### Enhancements from Bittensor Subnet - -While decentralization poses challenges, slowing development compared to centralized solutions, Akeru.ai embraces these challenges to ensure a decentralized and equitable AI ecosystem. Security features include: - -- **Watermarking**: Verifies LLM authenticity. -- **Network Pings**: Regular checks to ensure miner availability. - ### Rewards for Miners - **Dynamic Model Popularity Rewards**: Miners offering popular models earn more. @@ -68,4 +57,3 @@ We welcome contributions of all forms, from code to documentation. Here’s how ## Visuals and Diagrams Coming soon are detailed diagrams illustrating our network architecture and API workflows, helping clarify the complexities of our systems. - diff --git a/docs/contributing.md b/docs/contributing.md index 77d0885..e71422f 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -24,7 +24,7 @@ Welcome to the Akeru.ai community! We are thrilled to have you onboard. This doc ### Project Overview and Code of Conduct -[Akeru.ai](https://www.akeru.ai) leverages the power of the Akeru AI edge network running on a Bittensor Subnet to offer transparent, safe, and highly available AI capabilities. We strive to maintain a welcoming and inclusive community. +Akeru.ai is an API platform that offers transparent, safe, and highly available AI capabilities. We strive to maintain a welcoming and inclusive community. Our [Code of Conduct](/docs/code_of_conduct.md) outlines expected behavior and practices to ensure a collaborative and respectful environment. @@ -32,8 +32,6 @@ Our [Code of Conduct](/docs/code_of_conduct.md) outlines expected behavior and p Setting up your development environment is the first step in contributing: -- **Subnet Setup Guide**: For working with the subnet, follow the instructions [here](https://github.com/GuiBibeau/akeru/blob/main/subnet/validator/README.md). - - **Platform Setup Guide**: Additional setup instructions for the platform will be provided soon. ### Contribution Process @@ -49,13 +47,12 @@ To contribute to [Akeru.ai](https://www.akeru.ai): ### Coding Conventions -[Akeru.ai](https://www.akeru.ai) follows Next.js linting standards in the Next app and plans to setup ESLint for the API and service mesh parts. For coding styles, we use Prettier. Our Prettier config will serve as the source of truth. View the full coding convention [here](/docs/coding_conventions.md). +[Akeru.ai](https://www.akeru.ai) follows Next.js linting standards in the Next app and plans to setup ESLint for the API and service mesh parts. For coding styles, we use Prettier. Our Prettier config will serve as the source of truth. View the full coding convention [here](/docs/coding_conventions.md). ### Building and Testing - **Website**: Automated by Vercel. - **API**: Currently under development for automated processes. -- **Subnet**: Managed externally; details to be provided later. ### Documentation Standards @@ -90,4 +87,4 @@ Plans are in development and will be documented accordingly. ### Security Practices -Detailed security protocols, especially for the subnet, are currently under development and will be added upon completion. +Detailed security protocols will be added upon completion. diff --git a/docs/issue_and_pr_template.md b/docs/issue_and_pr_template.md index ac05e44..739e8e8 100644 --- a/docs/issue_and_pr_template.md +++ b/docs/issue_and_pr_template.md @@ -43,7 +43,6 @@ The README file is the first point of interaction for potential users and contri - Expand Introduction: Clearly define what Akeru.ai does, the problems it solves, and how it's distinct from other AI platforms. - Detail API Features: Include descriptions of current and upcoming features, expected use cases, and any associated limitations or costs. -- Clarify Bittensor Subnet Integration: Explain how the subnet supports the platform, including any security features and the advantages of decentralization. - Update Self-Hosting and Validation Sections: Provide preliminary guidelines and expectations for users interested in self-hosting or participating as validators/miners. - Visual Enhancements: Propose the creation of diagrams and workflows to help visually explain complex concepts. @@ -62,6 +61,7 @@ The README file is the first point of interaction for potential users and contri - @username2 (Technical Reviewer) ## Akeru.ai Pull Request Template + **Title**: [Descriptive title reflecting the main change] ### Description @@ -82,6 +82,7 @@ Briefly describe the purpose of the pull request and what it aims to achieve. In - Tag reviewers who are familiar with the impacted areas of the codebase. ### Checklist + - [ ] I have written tests. - [ ] My code does not produce new errors. - [ ] I gave myself a code review before asking others. @@ -89,14 +90,15 @@ Briefly describe the purpose of the pull request and what it aims to achieve. In ### Example #### Title: Update README for Enhanced Project Clarity and Information + #### Description -This pull request updates the README to provide a clearer and more comprehensive understanding of Akeru.ai. The enhancements include detailed descriptions of new API features, the Bittensor Subnet integration, and improved section formatting. These updates aim to make the repository more welcoming and informative for new contributors and users. + +This pull request updates the README to provide a clearer and more comprehensive understanding of Akeru.ai. The enhancements include detailed descriptions of new API features, and improved section formatting. These updates aim to make the repository more welcoming and informative for new contributors and users. #### Key Changes - Expanded Introduction: Better explains Akeru.ai's unique features and its differentiation in the AI space. - Detailed API Features: Includes use cases and development status for upcoming features. -- Clarified Bittensor Subnet Integration: Details on how the Bittensor Subnet enhances Akeru.ai’s capabilities, including security features and decentralized benefits. - Preliminary Self-Hosting Info: Provides initial guidelines on self-hosting and the requirements for validators and miners. #### Link to Issues @@ -104,6 +106,7 @@ This pull request updates the README to provide a clearer and more comprehensive - Addresses feedback from issue #[issue_number] for more detailed documentation. #### Request for Review + - @username1 - @username2 diff --git a/subnet/miner-cloudflare/.gitignore b/subnet/miner-cloudflare/.gitignore deleted file mode 100644 index 524488f..0000000 --- a/subnet/miner-cloudflare/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -bittensor_subnet_template.egg-info -__pycache__ \ No newline at end of file diff --git a/subnet/miner-cloudflare/README.md b/subnet/miner-cloudflare/README.md deleted file mode 100644 index a982ebb..0000000 --- a/subnet/miner-cloudflare/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# Akeru Compute Subnet - -The Akeru Compute Subnet is a market place incentivizing compute resources to the most in demand open source AI models. - -## Getting started - -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. start the miner: - -API only mode - -``` -python main.py -``` diff --git a/subnet/miner-cloudflare/__init__.py b/subnet/miner-cloudflare/__init__.py deleted file mode 100644 index ecd1226..0000000 --- a/subnet/miner-cloudflare/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -__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])) -) diff --git a/subnet/miner-cloudflare/api/__init__.py b/subnet/miner-cloudflare/api/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/subnet/miner-cloudflare/api/api.py b/subnet/miner-cloudflare/api/api.py deleted file mode 100644 index 71d622b..0000000 --- a/subnet/miner-cloudflare/api/api.py +++ /dev/null @@ -1,33 +0,0 @@ -import asyncio -from fastapi import FastAPI -from pydantic import BaseModel -from miner.miner import miner -from certification.certification_manager import run_certification_manager - - -class ChatRequest(BaseModel): - messages: list - model: str - -app = FastAPI() - -@app.get("/") -def index(): - return "ok" - - -@app.post("/chat") -async def chat(request: ChatRequest): - print(request) - messages = request.messages - model = request.model - - response = await miner.prompt(messages=messages, model=model) - messages.append({"role": "system", "content": response}) - - return messages - - -@app.on_event("startup") -async def startup_event(): - asyncio.create_task(run_certification_manager()) diff --git a/subnet/miner-cloudflare/certification/__init__.py b/subnet/miner-cloudflare/certification/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/subnet/miner-cloudflare/certification/certification_manager.py b/subnet/miner-cloudflare/certification/certification_manager.py deleted file mode 100644 index 4366e75..0000000 --- a/subnet/miner-cloudflare/certification/certification_manager.py +++ /dev/null @@ -1,65 +0,0 @@ -import aiohttp -import os -import asyncio -from .hash import hash_multiple_files -from dotenv import load_dotenv -import urllib.parse - - -class CertificationManager: - current_hash: str - image_signature: str - service_mesh_url: str - _certificate: str = None - - def __init__(self): - """ Initialize the CertificationManager class. """ - load_dotenv() - self.current_hash = hash_multiple_files(['main.py', 'protocol.py', './api/api.py']) - self.image_signature = os.getenv("DOCKER_IMAGE_SIGNATURE", '') - self.service_mesh_url = os.getenv('SERVICE_MESH_URL') - - async def run(self): - """ Run the CertificationManager and start the certification process """ - await self._get_certificate() - - async def _get_certificate(self): - """ Get the renewed certificate """ - try: - async with aiohttp.ClientSession() as session: - - # we must get the certificate for the current docker image and proove the right code is present. - params = { - "hash": self.current_hash, - "imageSignature": self.image_signature - } - # encode parameters - search = urllib.parse.urlencode(params) - - async with session.get(f"{self.service_mesh_url}/api/certification?{search}", ) as response: - if response.status == 200: - self._certificate = await response.text() - else: - print(f"Error getting certificate") - except aiohttp.ClientError as e: - # Handle any errors that occur during the request - print(f"Error discovering miners: {e}") - except Exception as e: - # Handle any other unexpected errors - print(f"Unexpected error: {e}") - - @property - def certificate(self): - return self._certificate - - - -certification_manager = CertificationManager() - - -async def run_certification_manager(): - while True: - await certification_manager.run() - # get recertified often to avoid getting the wrong rotation of certificate - await asyncio.sleep(120) - diff --git a/subnet/miner-cloudflare/certification/hash.py b/subnet/miner-cloudflare/certification/hash.py deleted file mode 100644 index e4d3645..0000000 --- a/subnet/miner-cloudflare/certification/hash.py +++ /dev/null @@ -1,14 +0,0 @@ -import hashlib - -def hash_multiple_files(file_paths): - """Generate MD5 hash for the concatenated content of multiple files.""" - md5 = hashlib.md5() - # Process each file in the list - for file_path in file_paths: - # Open each file in binary read mode - with open(file_path, "rb") as file: - # Read and update hash string value in blocks of 4K - for chunk in iter(lambda: file.read(4096), b""): - md5.update(chunk) - # Return the hexadecimal MD5 hash of the concatenated content - return md5.hexdigest() diff --git a/subnet/miner-cloudflare/config.py b/subnet/miner-cloudflare/config.py deleted file mode 100644 index 163b424..0000000 --- a/subnet/miner-cloudflare/config.py +++ /dev/null @@ -1,123 +0,0 @@ -import bittensor as bt -import argparse -import os -import socket - - -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_ip_address(): - hostname = socket.gethostname() - ip_address = socket.gethostbyname(hostname) - return ip_address - - -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 diff --git a/subnet/miner-cloudflare/dockerfile b/subnet/miner-cloudflare/dockerfile deleted file mode 100644 index 32157c9..0000000 --- a/subnet/miner-cloudflare/dockerfile +++ /dev/null @@ -1,10 +0,0 @@ -FROM python:3.10-slim - -WORKDIR /code - -COPY . /code - -RUN python -m pip install -e . - -ENTRYPOINT ["python"] -CMD ["main.py"] diff --git a/subnet/miner-cloudflare/main.py b/subnet/miner-cloudflare/main.py deleted file mode 100644 index 7ab6671..0000000 --- a/subnet/miner-cloudflare/main.py +++ /dev/null @@ -1,11 +0,0 @@ -import os -from dotenv import load_dotenv -from api.api import app - - - - -if __name__ == "__main__": - load_dotenv() - import uvicorn - uvicorn.run(app, host="0.0.0.0", port=os.getenv('PORT', 8080)) diff --git a/subnet/miner-cloudflare/miner/__init__.py b/subnet/miner-cloudflare/miner/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/subnet/miner-cloudflare/miner/miner.py b/subnet/miner-cloudflare/miner/miner.py deleted file mode 100644 index 47ea728..0000000 --- a/subnet/miner-cloudflare/miner/miner.py +++ /dev/null @@ -1,33 +0,0 @@ -import argparse -import aiohttp -import bittensor as bt -from dotenv import load_dotenv -from protocol import StreamPrompting - -from miner.stream_miner import StreamMiner - -load_dotenv() - -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 - - async def prompt(self, messages, model) -> StreamPrompting: - async with aiohttp.ClientSession() as session: - response = await session.post( - f"https://api.cloudflare.com/client/v4/accounts/{self.CLOUDFLARE_ACCOUNT_ID}/ai/run/@cf/meta/{model}", - headers={"Authorization": f"Bearer {self.CLOUDFLARE_AUTH_TOKEN}"}, - json={ - "messages": messages - } - ) - json_resp = await response.json() - - return json_resp['result']['response'] - -miner = Miner() diff --git a/subnet/miner-cloudflare/miner/stream_miner.py b/subnet/miner-cloudflare/miner/stream_miner.py deleted file mode 100644 index 6d7d1a4..0000000 --- a/subnet/miner-cloudflare/miner/stream_miner.py +++ /dev/null @@ -1,278 +0,0 @@ -import copy -import json -import os -import time -import asyncio -import argparse -import threading -import traceback -from abc import ABC, abstractmethod -from urllib.parse import urlencode, urljoin -import uuid - - -import bittensor as bt -from typing import Dict, Tuple - -from protocol import StreamPrompting - -from config import check_config, get_config -from dotenv import load_dotenv -from requests import post - - -class StreamMiner(ABC): - @property - def subtensor_connected(self): - return hasattr(self, 'subtensor') and self.subtensor is not None - - def __init__(self, config=None, axon=None, wallet=None, subtensor=None): - # load env variables - load_dotenv() - self.api_only = os.getenv('API_ONLY', 'True') - - self.CLOUDFLARE_AUTH_TOKEN = os.getenv('CLOUDFLARE_AUTH_TOKEN') - self.CLOUDFLARE_ACCOUNT_ID = os.getenv('CLOUDFLARE_ACCOUNT_ID') - # Setup base config from Miner.config() and merge with subclassed config. - base_config = copy.deepcopy(config or get_config()) - self.config = self.config() - self.config.merge(base_config) - - self.miner_services = { - "type": 'cloudflare', - "models": ['llama-2-7b-chat-int8', 'mistral-7b-instruct-v0.1'], - "address": os.getenv('MINER_ADDRESS') - } - - check_config(StreamMiner, self.config) - bt.logging.info(self.config) - - self.prompt_cache: Dict[str, Tuple[str, int]] = {} - - if self.api_only != 'True': - # Activating Bittensor's logging with the set configurations. - bt.logging(config=self.config, logging_dir=self.config.full_path) - bt.logging.info("Setting up bittensor objects.") - - # Wallet holds cryptographic information, ensuring secure transactions and communication. - self.wallet = wallet or bt.wallet(config=self.config) - print(self.wallet) - bt.logging.info(f"Wallet {self.wallet}") - - # subtensor manages the blockchain connection, facilitating interaction with the Bittensor blockchain. - self.subtensor = subtensor or bt.subtensor(config=self.config) - bt.logging.info(f"Subtensor: {self.subtensor}") - bt.logging.info( - f"Running miner for subnet: {self.config.netuid} on network: {self.subtensor.chain_endpoint} with config:" - ) - - # metagraph provides the network's current state, holding state about other participants in a subnet. - self.metagraph = self.subtensor.metagraph(self.config.netuid) - print(self.metagraph) - bt.logging.info(f"Metagraph: {self.metagraph}") - - if self.wallet.hotkey.ss58_address not in self.metagraph.hotkeys: - bt.logging.error( - f"\nYour validator: {self.wallet} if not registered to chain connection: {self.subtensor} \nRun btcli register and try again. " - ) - exit() - else: - # Each miner gets a unique identity (UID) in the network for differentiation. - self.my_subnet_uid = self.metagraph.hotkeys.index( - self.wallet.hotkey.ss58_address - ) - - # identify to the edge compute network service discovery - - # TODO replace with hosted endpoint of service map - url = os.getenv('SERVICE_MESH_URL') - secret = os.getenv('SERVICE_MESH_SECRET_KEY') - # for now miners are allow listed manually and given a secret key to identify - headers = {'Content-Type': 'application/json', - 'Authorization': f'Bearer {secret}'} - - service_map_dict = { - # must map the netuid for validation by validators later - "netuid": self.my_subnet_uid, - "hotkey": self.wallet.hotkey.ss58_address, - **self.miner_services - } - - json.dumps(service_map_dict) - - # send to the service map - post(f'{url}/api/miner', - data=json.dumps(service_map_dict), headers=headers) - bt.logging.info(f"Running miner on uid: {self.my_subnet_uid}") - - else: - self.uuid = os.getenv('UUID') or uuid.uuid4() - url = os.getenv('SERVICE_MESH_URL') - secret = os.getenv('SERVICE_MESH_SECRET_KEY') - # for now miners are allow listed manually and given a secret key to identify - headers = {'Content-Type': 'application/json', - 'Authorization': f'Bearer {secret}'} - - service_map_dict = { - # must map the netuid for validation by validators later - "uid": str(self.uuid), - **self.miner_services - } - - # Base URL - base_url = urljoin(url, '/api/miner') - - # Query parameters - params = {'api-only': 'true'} - - # Construct the full URL with query parameters - full_url = f"{base_url}?{urlencode(params)}" - data = json.dumps(service_map_dict) - # send to the service map - post(full_url, data=data, headers=headers) - - # Instantiate runners - self.should_exit: bool = False - self.is_running: bool = False - self.thread: threading.Thread = None - self.lock = asyncio.Lock() - self.request_timestamps: Dict = {} - - @ abstractmethod - def config(self) -> "bt.Config": - ... - - @ classmethod - @ abstractmethod - def add_args(cls, parser: argparse.ArgumentParser): - ... - - def verify_fn(self, synapse: StreamPrompting) -> None: - return True - - def _prompt(self, synapse: StreamPrompting) -> StreamPrompting: - print('received a prompt') - messages = synapse.messages - prompt_key = ''.join(prompt['content'] for prompt in messages) - print(messages) - - if prompt_key in self.prompt_cache: - response, timestamp = self.prompt_cache[prompt_key] - if time.time() - timestamp < 60: - raise ValueError( - f"Prompt '{prompt_key}' was sent recently and is cached. Skipping processing.") - - synapse = self.prompt(synapse) - - self.prompt_cache[prompt_key] = (synapse.completion, time.time()) - return synapse - - @ abstractmethod - def prompt(self, synapse: StreamPrompting) -> StreamPrompting: - ... - - def run(self): - """ - Runs the miner logic. This method starts the miner's operations, including - listening for incoming requests and periodically updating the miner's knowledge - of the network graph. - """ - if not self.subtensor.is_hotkey_registered( - netuid=self.config.netuid, - hotkey_ss58=self.wallet.hotkey.ss58_address, - ): - bt.logging.error( - f"Wallet: {self.wallet} is not registered on netuid {self.config.netuid}" - f"Please register the hotkey using `btcli subnets register` before trying again" - ) - exit() - - # --- Run until should_exit = True. - self.last_epoch_block = self.subtensor.get_current_block() - bt.logging.info(f"Miner starting at block: {self.last_epoch_block}") - - # This loop maintains the miner's operations until intentionally stopped. - bt.logging.info(f"Starting main loop") - step = 0 - try: - while not self.should_exit: - start_epoch = time.time() - - # --- Wait until next epoch. - current_block = self.subtensor.get_current_block() - while ( - current_block - self.last_epoch_block - < self.config.miner.blocks_per_epoch - ): - # --- Wait for next bloc. - time.sleep(1) - current_block = self.subtensor.get_current_block() - - # --- Check if we should exit. - if self.should_exit: - break - - # --- Update the metagraph with the latest network state. - self.last_epoch_block = self.subtensor.get_current_block() - - metagraph = self.subtensor.metagraph( - netuid=self.config.netuid, - lite=True, - block=self.last_epoch_block, - ) - log = ( - f"Step:{step} | " - f"Block:{metagraph.block.item()} | " - f"Stake:{metagraph.S[self.my_subnet_uid]} | " - f"Rank:{metagraph.R[self.my_subnet_uid]} | " - f"Trust:{metagraph.T[self.my_subnet_uid]} | " - f"Consensus:{metagraph.C[self.my_subnet_uid] } | " - f"Incentive:{metagraph.I[self.my_subnet_uid]} | " - f"Emission:{metagraph.E[self.my_subnet_uid]}" - ) - bt.logging.info(log) - - step += 1 - - # If someone intentionally stops the miner, it'll safely terminate operations. - except KeyboardInterrupt: - bt.logging.success("Miner killed by keyboard interrupt.") - exit() - - # In case of unforeseen errors, the miner will log the error and continue operations. - except Exception as e: - bt.logging.error(traceback.format_exc()) - - def run_in_background_thread(self): - """ - Starts the miner's operations in a separate background thread. - This is useful for non-blocking operations. - """ - if not self.is_running: - bt.logging.debug("Starting miner in background thread.") - self.should_exit = False - self.thread = threading.Thread(target=self.run, daemon=True) - self.thread.start() - self.is_running = True - bt.logging.debug("Started") - - def stop_run_thread(self): - """ - Stops the miner's operations that are running in the background thread. - """ - if self.is_running: - bt.logging.debug("Stopping miner in background thread.") - self.should_exit = True - self.thread.join(5) - self.is_running = False - bt.logging.debug("Stopped") - - def __enter__(self): - """ - Starts the miner's operations in a background thread upon entering the context. - This method facilitates the use of the miner in a 'with' statement. - """ - self.run_in_background_thread() - - def __exit__(self, exc_type, exc_value, traceback): - self.stop_run_thread() diff --git a/subnet/miner-cloudflare/protocol.py b/subnet/miner-cloudflare/protocol.py deleted file mode 100644 index cc20ef4..0000000 --- a/subnet/miner-cloudflare/protocol.py +++ /dev/null @@ -1,59 +0,0 @@ -import pydantic -import bittensor as bt -import json - -from typing import List - - -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", "model"], - 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.", - ) - - def to_dict(self): - """ - Converts the class to a dictionary. - - Returns: - dict: A dictionary representation of the class. - """ - return { - "messages": self.messages, - "required_hash_fields": self.required_hash_fields, - "completion": self.completion, - "model": self.model, - } - - def to_json(self): - """ - Converts the class to a JSON string. - - Returns: - str: A JSON string representation of the class. - """ - return json.dumps(self.to_dict()) diff --git a/subnet/miner-cloudflare/requirements.txt b/subnet/miner-cloudflare/requirements.txt deleted file mode 100644 index 807e4e9..0000000 --- a/subnet/miner-cloudflare/requirements.txt +++ /dev/null @@ -1,9 +0,0 @@ -bittensor -torch -pydantic -python-dotenv -simplejson -requests-async -gunicorn -aiohttp -fastapi \ No newline at end of file diff --git a/subnet/miner-cloudflare/setup.py b/subnet/miner-cloudflare/setup.py deleted file mode 100644 index 011d397..0000000 --- a/subnet/miner-cloudflare/setup.py +++ /dev/null @@ -1,82 +0,0 @@ -import re -import os -import codecs -import pathlib -from os import path -from io import open -from setuptools import setup, find_packages -from pkg_resources import parse_requirements - - -def read_requirements(path): - with open(path, "r") as f: - requirements = f.read().splitlines() - processed_requirements = [] - - for req in requirements: - # For git or other VCS links - if req.startswith("git+") or "@" in req: - pkg_name = re.search(r"(#egg=)([\w\-_]+)", req) - if pkg_name: - processed_requirements.append(pkg_name.group(2)) - else: - # You may decide to raise an exception here, - # if you want to ensure every VCS link has an #egg= at the end - continue - else: - processed_requirements.append(req) - return processed_requirements - - -requirements = read_requirements("requirements.txt") -here = path.abspath(path.dirname(__file__)) - -with open(path.join(here, "README.md"), encoding="utf-8") as f: - long_description = f.read() - -# loading version from setup.py -with codecs.open( - os.path.join(here, "./__init__.py"), encoding="utf-8" -) as init_file: - version_match = re.search( - r"^__version__ = ['\"]([^'\"]*)['\"]", init_file.read(), re.M - ) - version_string = version_match.group(1) - -setup( - # TODO(developer): Change this value to your module subnet name. - name="bittensor_subnet_template", - version=version_string, - # TODO(developer): Change this value to your module subnet description. - description="bittensor_subnet_template", - long_description=long_description, - long_description_content_type="text/markdown", - # TODO(developer): Change this url to your module subnet github url. - url="https://github.com/opentensor/bittensor-subnet-template", - # TODO(developer): Change this value to your module subnet author name. - author="bittensor.com", - packages=find_packages(), - include_package_data=True, - # TODO(developer): Change this value to your module subnet author email. - author_email="", - license="MIT", - python_requires=">=3.8", - install_requires=requirements, - classifiers=[ - "Development Status :: 3 - Alpha", - "Intended Audience :: Developers", - "Topic :: Software Development :: Build Tools", - # Pick your license as you wish - "License :: OSI Approved :: MIT License", - "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Topic :: Scientific/Engineering", - "Topic :: Scientific/Engineering :: Mathematics", - "Topic :: Scientific/Engineering :: Artificial Intelligence", - "Topic :: Software Development", - "Topic :: Software Development :: Libraries", - "Topic :: Software Development :: Libraries :: Python Modules", - ], -) diff --git a/subnet/validator/.gitignore b/subnet/validator/.gitignore deleted file mode 100644 index 524488f..0000000 --- a/subnet/validator/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -bittensor_subnet_template.egg-info -__pycache__ \ No newline at end of file diff --git a/subnet/validator/Dockerfile b/subnet/validator/Dockerfile deleted file mode 100644 index b40a04f..0000000 --- a/subnet/validator/Dockerfile +++ /dev/null @@ -1,12 +0,0 @@ -FROM python:3.10-slim - -WORKDIR /code - -COPY . /code -# COPY ./requirements.txt /code/requirements.txt -# COPY ./setup.py /code/setup.py - -RUN python -m pip install -e . - - -CMD ["python", "main.py"] diff --git a/subnet/validator/README.md b/subnet/validator/README.md deleted file mode 100644 index 496ff5c..0000000 --- a/subnet/validator/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# Akeru Compute Subnet - -The Akeru Compute Subnet is a market place incentivizing compute resources to the most in demand open source AI models. - -## Getting started - -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. start the validator: - -API only mode - -``` -python main.py -``` diff --git a/subnet/validator/__init__.py b/subnet/validator/__init__.py deleted file mode 100644 index ecd1226..0000000 --- a/subnet/validator/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -__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])) -) diff --git a/subnet/validator/main.py b/subnet/validator/main.py deleted file mode 100644 index 7741279..0000000 --- a/subnet/validator/main.py +++ /dev/null @@ -1,92 +0,0 @@ -import asyncio -import os -import bittensor as bt -from fastapi.params import Depends -import torch -from miner_manager import MinerManager -from validator import BaseValidatorNeuron -from fastapi import FastAPI, HTTPException, Request -from fastapi.security import OAuth2PasswordBearer, SecurityScopes -import aiohttp -from reward import calculate_total_message_length, get_reward -from typing import TypedDict, List -from dotenv import load_dotenv - -load_dotenv() - - -api_only = os.getenv('API_ONLY') -VALIDATOR_SECRET = os.getenv('VALIDATOR_SECRET') - - -miner_manager = MinerManager(api_only=api_only == 'True') - - -async def run_miner_manager(): - while True: - await miner_manager.run() - await asyncio.sleep(10) - - -class Miner(TypedDict): - address: str - hotkey: str - models: List[str] - netuid: str - type: str - - -class Validator(BaseValidatorNeuron): - def __init__(self, config=None): - super(Validator, self).__init__(config=config) - bt.logging.info("load_state()") - if api_only == 'False': - self.load_state() - - -app = FastAPI() -validator = Validator() - - -@app.get('/') -async def index(): - return "OK" - -oauth2_scheme = OAuth2PasswordBearer( - tokenUrl="auth/token", scopes={"chat": "Access to chat endpoint"}) - - -@app.post("/chat") -async def chat(request: Request, token: str = Depends(oauth2_scheme)): - if token != VALIDATOR_SECRET: - raise HTTPException(status_code=401, detail="Invalid token") - data = await request.json() - model = data['model'] - miner = miner_manager.get_fastest_miner_for_model(model=model) - miner_id = miner["id"] - prompt_len = calculate_total_message_length(data) - - async with aiohttp.ClientSession() as session: - url = miner['address'] - async with session.post(f'{url}/chat', json=data) as resp: - response = await resp.json() - completion_len = len(response[-1]) - - reward = get_reward( - model=model, completion_len=completion_len, prompt_len=prompt_len) - print(f'reward for prompt: {reward}') - if (validator.subtensor_connected): - validator.update_scores( - torch.FloatTensor([reward]), [int(miner_id)]) - - return response - - -@app.on_event("startup") -async def startup_event(): - asyncio.create_task(run_miner_manager()) - -# The main function parses the configuration and runs the validator. -if __name__ == "__main__": - import uvicorn - uvicorn.run(app, host="0.0.0.0", port=os.getenv('PORT', 8080)) diff --git a/subnet/validator/miner_manager.py b/subnet/validator/miner_manager.py deleted file mode 100644 index e4173fd..0000000 --- a/subnet/validator/miner_manager.py +++ /dev/null @@ -1,158 +0,0 @@ -import os -from urllib.parse import urlencode -import aiohttp -import asyncio -import time -from dotenv import load_dotenv - - -class MinerManager: - def __init__(self, api_only): - """ - Initialize the MinerManager class. - - Args: - api_only (bool): If True, we are running without bittensor operatons. - """ - load_dotenv() - self.miners = {} # Dictionary to store miners - self.api_only = api_only - self.bearer_token = os.getenv('SERVICE_MESH_SECRET_KEY') - - async def run(self): - """ - Run the MinerManager and start the discovery and ping process. - """ - await self._discover_and_ping_miners() - - async def _discover_and_ping_miners(self): - """ - Discover and ping miners periodically. - """ - while True: - try: - async with aiohttp.ClientSession() as session: - url = os.getenv('SERVICE_MESH_URL') - headers = {"Authorization": f"Bearer {self.bearer_token}"} - params = {'api-only': 'true' if self.api_only else 'false'} - async with session.get(f"{url}/api/miner?{urlencode(params)}", headers=headers) as response: - if response.status == 200: - miners_data = await response.json() - for miner_data in miners_data: - miner_id = miner_data["uid"] if "uid" in miner_data else miner_data["netuid"] - miner_address = miner_data["address"] - miner_models = miner_data.get("models") - if miner_id not in self.miners: - self.miners[miner_id] = { - "address": miner_address, - "models": miner_models, - "id": miner_id, - "last_ping": None, - "ping_time": None - } - - # Ping all the miners - ping_tasks = [] - for miner_id in list(self.miners.keys()): - ping_task = asyncio.create_task( - self._ping_miner(session, miner_id)) - ping_tasks.append(ping_task) - self._update_model_miners() - - # Wait for all the ping tasks to complete - await asyncio.gather(*ping_tasks) - - else: - print( - f"Error discovering miners: {response.status}") - - # Sleep for 10 minutes before the next discovery and ping iteration - await asyncio.sleep(600) - - except aiohttp.ClientError as e: - # Handle any errors that occur during the request - print(f"Error discovering miners: {e}") - await asyncio.sleep(60) # Sleep for 1 minute before retrying - - except Exception as e: - # Handle any other unexpected errors - print(f"Unexpected error: {e}") - await asyncio.sleep(60) # Sleep for 1 minute before retrying - - async def _ping_miner(self, session, miner_id): - """ - Ping a miner to check its availability and update its ping time. - - Args: - session (aiohttp.ClientSession): The session to use for the request. - miner_id (str): The ID of the miner to ping. - """ - try: - start_time = time.time() - async with session.get(f"{self.miners[miner_id]['address']}/") as response: - if response.status == 200: - end_time = time.time() - ping_time = end_time - start_time - self.miners[miner_id]["last_ping"] = end_time - self.miners[miner_id]["ping_time"] = ping_time - else: - print(f"Error pinging miner {miner_id}: {response.status}") - # Remove the miner if the ping is unsuccessful - del self.miners[miner_id] - - except aiohttp.ClientError as e: - # Handle any errors that occur during the ping request - print(f"Error pinging miner {miner_id}: {e}") - del self.miners[miner_id] # Remove the miner if the ping fails - - except Exception as e: - # Handle any other unexpected errors - print(f"Unexpected error: {e}") - # Remove the miner if an unexpected error occurs - del self.miners[miner_id] - - def _update_model_miners(self): - """ - Update the model_miners dictionary with the available miners and their ping times. - """ - self.model_miners = {} - for miner_id, miner_data in self.miners.items(): - models = miner_data.get("models") - ping_time = miner_data.get("ping_time") - if models: - for model in models: - if model not in self.model_miners: - self.model_miners[model] = [] - self.model_miners[model].append({ - "id": miner_id, - "ping_time": ping_time - }) - - def get_fastest_miner_for_model(self, model): - """ - Get the fastest miner for a given model. - - Args: - model (str): The name of the model. - - Returns: - dict: The details of the fastest miner for the given model, or None if no miner is available. - """ - if model in self.model_miners: - miners = self.model_miners[model] - fastest_miner = min(miners, key=lambda x: x['ping_time']) - print(fastest_miner) - id = fastest_miner['id'] - miner_details = self.miners[id] - return miner_details - else: - return None - - def get_miners(self): - """ - Get the dictionary of all miners. - - Returns: - dict: The dictionary of all miners, with their details. - """ - return self.miners diff --git a/subnet/validator/mock.py b/subnet/validator/mock.py deleted file mode 100644 index 2b027ff..0000000 --- a/subnet/validator/mock.py +++ /dev/null @@ -1,122 +0,0 @@ -import time - -import asyncio -import random -import bittensor as bt - -from typing import List - - -class MockSubtensor(bt.MockSubtensor): - def __init__(self, netuid, n=16, wallet=None, network="mock"): - super().__init__(network=network) - - if not self.subnet_exists(netuid): - self.create_subnet(netuid) - - # Register ourself (the validator) as a neuron at uid=0 - if wallet is not None: - self.force_register_neuron( - netuid=netuid, - hotkey=wallet.hotkey.ss58_address, - coldkey=wallet.coldkey.ss58_address, - balance=100000, - stake=100000, - ) - - # Register n mock neurons who will be miners - for i in range(1, n + 1): - self.force_register_neuron( - netuid=netuid, - hotkey=f"miner-hotkey-{i}", - coldkey="mock-coldkey", - balance=100000, - stake=100000, - ) - - -class MockMetagraph(bt.metagraph): - def __init__(self, netuid=1, network="mock", subtensor=None): - super().__init__(netuid=netuid, network=network, sync=False) - - if subtensor is not None: - self.subtensor = subtensor - self.sync(subtensor=subtensor) - - for axon in self.axons: - axon.ip = "127.0.0.0" - axon.port = 8091 - - bt.logging.info(f"Metagraph: {self}") - bt.logging.info(f"Axons: {self.axons}") - - -class MockDendrite(bt.dendrite): - """ - Replaces a real bittensor network request with a mock request that just returns some static response for all axons that are passed and adds some random delay. - """ - - def __init__(self, wallet): - super().__init__(wallet) - - async def forward( - self, - axons: List[bt.axon], - synapse: bt.Synapse = bt.Synapse(), - timeout: float = 12, - deserialize: bool = True, - run_async: bool = True, - streaming: bool = False, - ): - if streaming: - raise NotImplementedError("Streaming not implemented yet.") - - async def query_all_axons(streaming: bool): - """Queries all axons for responses.""" - - async def single_axon_response(i, axon): - """Queries a single axon for a response.""" - - start_time = time.time() - s = synapse.copy() - # Attach some more required data so it looks real - s = self.preprocess_synapse_for_request(axon, s, timeout) - # We just want to mock the response, so we'll just fill in some data - process_time = random.random() - if process_time < timeout: - s.dendrite.process_time = str(time.time() - start_time) - # Update the status code and status message of the dendrite to match the axon - # TODO (developer): replace with your own expected synapse data - s.dummy_output = s.dummy_input * 2 - s.dendrite.status_code = 200 - s.dendrite.status_message = "OK" - synapse.dendrite.process_time = str(process_time) - else: - s.dummy_output = 0 - s.dendrite.status_code = 408 - s.dendrite.status_message = "Timeout" - synapse.dendrite.process_time = str(timeout) - - # Return the updated synapse object after deserializing if requested - if deserialize: - return s.deserialize() - else: - return s - - return await asyncio.gather( - *( - single_axon_response(i, target_axon) - for i, target_axon in enumerate(axons) - ) - ) - - return await query_all_axons(streaming) - - 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 "MockDendrite({})".format(self.keypair.ss58_address) diff --git a/subnet/validator/neuron.py b/subnet/validator/neuron.py deleted file mode 100644 index 26d0999..0000000 --- a/subnet/validator/neuron.py +++ /dev/null @@ -1,169 +0,0 @@ -import copy -import os - -import bittensor as bt - -from abc import ABC, abstractmethod - -# Sync calls set weights and also resyncs the metagraph. -from mock import MockMetagraph, MockSubtensor -from utils.config import check_config, add_args, config -from utils.misc import ttl_get_block - -__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])) -) - - -class BaseNeuron(ABC): - - neuron_type: str = "BaseNeuron" - - @property - def subtensor_connected(self): - return hasattr(self, 'subtensor') and self.subtensor is not None - - @classmethod - def check_config(cls, config: "bt.Config"): - check_config(cls, config) - - @classmethod - def add_args(cls, parser): - add_args(cls, parser) - - @classmethod - def config(cls): - return config(cls) - - subtensor: "bt.subtensor" - wallet: "bt.wallet" - metagraph: "bt.metagraph" - spec_version: int = spec_version - - @property - def block(self): - return ttl_get_block(self) - - def __init__(self, config=None): - base_config = copy.deepcopy(config or BaseNeuron.config()) - self.config = self.config() - self.config.merge(base_config) - self.check_config(self.config) - - # Set up logging with the provided configuration and directory. - bt.logging(config=self.config, logging_dir=self.config.full_path) - - # If a gpu is required, set the device to cuda:N (e.g. cuda:0) - self.device = self.config.neuron.device - - # Log the configuration for reference. - bt.logging.info(self.config) - - # Build Bittensor objects - # These are core Bittensor classes to interact with the network. - bt.logging.info("Setting up bittensor objects.") - - # The wallet holds the cryptographic key pairs for the miner. - if self.config.mock: - self.wallet = bt.MockWallet(config=self.config) - else: - self.wallet = bt.wallet(config=self.config) - - if os.getenv('API_ONLY') == 'False': - self.instantiate_subtensor_and_metagraph() - - if self.subtensor_connected: - bt.logging.info(f"Wallet: {self.wallet}") - bt.logging.info(f"Subtensor: {self.subtensor}") - bt.logging.info(f"Metagraph: {self.metagraph}") - - # Check if the miner is registered on the Bittensor network before proceeding further. - self.check_registered() - - # Each miner gets a unique identity (UID) in the network for differentiation. - self.uid = self.metagraph.hotkeys.index( - self.wallet.hotkey.ss58_address - ) - bt.logging.info( - f"Running neuron on subnet: {self.config.netuid} with uid {self.uid} using network: {self.subtensor.chain_endpoint}" - ) - self.step = 0 - - def instantiate_subtensor_and_metagraph(self): - if self.config.mock: - self.subtensor = MockSubtensor( - self.config.netuid, wallet=self.wallet - ) - self.metagraph = MockMetagraph( - self.config.netuid, subtensor=self.subtensor - ) - else: - self.subtensor = bt.subtensor(config=self.config) - self.metagraph = self.subtensor.metagraph(self.config.netuid) - - def sync(self): - """ - Wrapper for synchronizing the state of the network for the given miner or validator. - """ - # Ensure miner or validator hotkey is still registered on the network. - self.check_registered() - - if self.should_sync_metagraph(): - self.resync_metagraph() - - if self.should_set_weights(): - self.set_weights() - - # Always save state. - self.save_state() - - def check_registered(self): - # --- Check for registration. - if not self.subtensor.is_hotkey_registered( - netuid=self.config.netuid, - hotkey_ss58=self.wallet.hotkey.ss58_address, - ): - bt.logging.error( - f"Wallet: {self.wallet} is not registered on netuid {self.config.netuid}." - f" Please register the hotkey using `btcli subnets register` before trying again" - ) - exit() - - def should_sync_metagraph(self): - """ - Check if enough epoch blocks have elapsed since the last checkpoint to sync. - """ - print('metagraph', self.metagraph.last_update[self.uid], self.block) - return ( - self.block - self.metagraph.last_update[self.uid] - ) > self.config.neuron.epoch_length - - def should_set_weights(self) -> bool: - # Don't set weights on initialization. - if self.step == 0: - return False - - # Check if enough epoch blocks have elapsed since the last epoch. - if self.config.neuron.disable_set_weights: - return False - - # Define appropriate logic for when set weights. - return ( - (self.block - self.metagraph.last_update[self.uid]) - > self.config.neuron.epoch_length - and self.neuron_type != "MinerNeuron" - ) # don't set weights if you're a miner - - def save_state(self): - bt.logging.warning( - "save_state() not implemented for this neuron. You can implement this function to save model checkpoints or other useful data." - ) - - def load_state(self): - bt.logging.warning( - "load_state() not implemented for this neuron. You can implement this function to load model checkpoints or other useful data." - ) diff --git a/subnet/validator/protocol.py b/subnet/validator/protocol.py deleted file mode 100644 index 4013b58..0000000 --- a/subnet/validator/protocol.py +++ /dev/null @@ -1,85 +0,0 @@ -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, - } diff --git a/subnet/validator/requirements.txt b/subnet/validator/requirements.txt deleted file mode 100644 index 3cdf2a2..0000000 --- a/subnet/validator/requirements.txt +++ /dev/null @@ -1,10 +0,0 @@ -bittensor -torch -pydantic -python-dotenv -simplejson -starlette -uvicorn -requests-async -loguru -zkpy \ No newline at end of file diff --git a/subnet/validator/reward.py b/subnet/validator/reward.py deleted file mode 100644 index bb6e485..0000000 --- a/subnet/validator/reward.py +++ /dev/null @@ -1,76 +0,0 @@ -import torch -from typing import List - -text_completion_models = { - "llama-2-7b-chat-int8": { - "name": "llama-2-7b-chat-int8", - "demand_multiplier": 0.8, - }, - "llama-2-7b-chat-fp16": { - "name": "llama-2-7b-chat-fp16", - "demand_multiplier": 0.85 - }, - "mistral-7b-instruct-v0.1": { - "name": "mistral-7b-instruct-v0.1", - "demand_multiplier": 0.85 - } -} - - -def calculate_total_message_length(data): - total_length = 0 - for message in data["messages"]: - total_length += len(message["content"]) - return total_length - - -def get_reward(model, completion_len, prompt_len) -> float: - print(model, completion_len, prompt_len) - # Define the maximum and minimum completion lengths in characters - max_completion_len = 4000 - min_completion_len = 200 - - # Define the maximum and minimum prompt lengths in characters - max_prompt_len = 2000 - min_prompt_len = 50 - - # Normalize the completion length - normalized_completion_len = float(( - completion_len - min_completion_len) / (max_completion_len - min_completion_len)) - - # Normalize the prompt length - normalized_prompt_len = float((prompt_len - min_prompt_len) / - (max_prompt_len - min_prompt_len)) - - # Get the demand multiplier for the model - demand_multiplier = text_completion_models[model]["demand_multiplier"] - - # Calculate the reward - reward = (demand_multiplier * 0.5) + \ - (normalized_completion_len * 0.3) + (normalized_prompt_len * 0.2) - - # Ensure the reward is between 0 and 1 - reward = max(0, min(1, reward)) - - return reward - - -def get_rewards( - self, - query: int, - responses: List[float], -) -> torch.FloatTensor: - """ - Returns a tensor of rewards for the given query and responses. - - Args: - - query (int): The query sent to the miner. - - responses (List[float]): A list of responses from the miner. - - Returns: - - torch.FloatTensor: A tensor of rewards for the given query and responses. - """ - # Get all the reward results by iteratively calling your reward() function. - return torch.FloatTensor( - [reward(query, response) for response in responses] - ).to(self.device) diff --git a/subnet/validator/setup.py b/subnet/validator/setup.py deleted file mode 100644 index 011d397..0000000 --- a/subnet/validator/setup.py +++ /dev/null @@ -1,82 +0,0 @@ -import re -import os -import codecs -import pathlib -from os import path -from io import open -from setuptools import setup, find_packages -from pkg_resources import parse_requirements - - -def read_requirements(path): - with open(path, "r") as f: - requirements = f.read().splitlines() - processed_requirements = [] - - for req in requirements: - # For git or other VCS links - if req.startswith("git+") or "@" in req: - pkg_name = re.search(r"(#egg=)([\w\-_]+)", req) - if pkg_name: - processed_requirements.append(pkg_name.group(2)) - else: - # You may decide to raise an exception here, - # if you want to ensure every VCS link has an #egg= at the end - continue - else: - processed_requirements.append(req) - return processed_requirements - - -requirements = read_requirements("requirements.txt") -here = path.abspath(path.dirname(__file__)) - -with open(path.join(here, "README.md"), encoding="utf-8") as f: - long_description = f.read() - -# loading version from setup.py -with codecs.open( - os.path.join(here, "./__init__.py"), encoding="utf-8" -) as init_file: - version_match = re.search( - r"^__version__ = ['\"]([^'\"]*)['\"]", init_file.read(), re.M - ) - version_string = version_match.group(1) - -setup( - # TODO(developer): Change this value to your module subnet name. - name="bittensor_subnet_template", - version=version_string, - # TODO(developer): Change this value to your module subnet description. - description="bittensor_subnet_template", - long_description=long_description, - long_description_content_type="text/markdown", - # TODO(developer): Change this url to your module subnet github url. - url="https://github.com/opentensor/bittensor-subnet-template", - # TODO(developer): Change this value to your module subnet author name. - author="bittensor.com", - packages=find_packages(), - include_package_data=True, - # TODO(developer): Change this value to your module subnet author email. - author_email="", - license="MIT", - python_requires=">=3.8", - install_requires=requirements, - classifiers=[ - "Development Status :: 3 - Alpha", - "Intended Audience :: Developers", - "Topic :: Software Development :: Build Tools", - # Pick your license as you wish - "License :: OSI Approved :: MIT License", - "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Topic :: Scientific/Engineering", - "Topic :: Scientific/Engineering :: Mathematics", - "Topic :: Scientific/Engineering :: Artificial Intelligence", - "Topic :: Software Development", - "Topic :: Software Development :: Libraries", - "Topic :: Software Development :: Libraries :: Python Modules", - ], -) diff --git a/subnet/validator/test.py b/subnet/validator/test.py deleted file mode 100644 index cbfe9d0..0000000 --- a/subnet/validator/test.py +++ /dev/null @@ -1,21 +0,0 @@ -from zkpy.circuit import Circuit, GROTH, PLONK, FFLONK - -# Define the circuit that checks if a string contains another string -circuit = Circuit("./contains_string.circom") -circuit.compile() - -# Generate the witness (proof) for the statement "the string 'I love Python Programming' contains the string 'Python'" -circuit.gen_witness({"str": "I love Python Programming", "sub": "Python"}) - -# Set up the proving system using the PLONK scheme and the powers of tau file -circuit.setup(PLONK, ptau_file="ptau.ptau") - -# Generate the zero-knowledge proof -circuit.prove(PLONK) - -# Export the verification key and proof -circuit.export_vkey("vkey.json") -circuit.export_proof("proof.json", "public.json") - -# Verify the proof -circuit.verify(PLONK, "vkey.json", "public.json", "proof.json") diff --git a/subnet/validator/uids.py b/subnet/validator/uids.py deleted file mode 100644 index c117349..0000000 --- a/subnet/validator/uids.py +++ /dev/null @@ -1,64 +0,0 @@ -import torch -import random -import bittensor as bt -from typing import List - - -def check_uid_availability( - metagraph: "bt.metagraph.Metagraph", uid: int, vpermit_tao_limit: int -) -> bool: - """Check if uid is available. The UID should be available if it is serving and has less than vpermit_tao_limit stake - Args: - metagraph (:obj: bt.metagraph.Metagraph): Metagraph object - uid (int): uid to be checked - vpermit_tao_limit (int): Validator permit tao limit - Returns: - bool: True if uid is available, False otherwise - """ - # Filter non serving axons. - if not metagraph.axons[uid].is_serving: - return False - # Filter validator permit > 1024 stake. - if metagraph.validator_permit[uid]: - if metagraph.S[uid] > vpermit_tao_limit: - return False - # Available otherwise. - return True - - -def get_random_uids( - self, k: int, exclude: List[int] = None -) -> torch.LongTensor: - """Returns k available random uids from the metagraph. - Args: - k (int): Number of uids to return. - exclude (List[int]): List of uids to exclude from the random sampling. - Returns: - uids (torch.LongTensor): Randomly sampled available uids. - Notes: - If `k` is larger than the number of available `uids`, set `k` to the number of available `uids`. - """ - print('-------------------------', self.metagraph.axons[0].is_serving) - # candidate_uids = [] - # avail_uids = [] - - # for uid in range(self.metagraph.n.item()): - # uid_is_available = check_uid_availability( - # self.metagraph, uid, self.config.neuron.vpermit_tao_limit - # ) - # uid_is_not_excluded = exclude is None or uid not in exclude - - # if uid_is_available: - # avail_uids.append(uid) - # if uid_is_not_excluded: - # candidate_uids.append(uid) - - # # Check if candidate_uids contain enough for querying, if not grab all avaliable uids - # available_uids = candidate_uids - # if len(candidate_uids) < k: - # available_uids += random.sample( - # [uid for uid in avail_uids if uid not in candidate_uids], - # k - len(candidate_uids), - # ) - uids = torch.tensor([0]) - return uids diff --git a/subnet/validator/utils/__init__.py b/subnet/validator/utils/__init__.py deleted file mode 100644 index 1e61220..0000000 --- a/subnet/validator/utils/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from . import config -from . import misc -from . import uids diff --git a/subnet/validator/utils/config.py b/subnet/validator/utils/config.py deleted file mode 100644 index a224e7c..0000000 --- a/subnet/validator/utils/config.py +++ /dev/null @@ -1,210 +0,0 @@ -import os -import torch -import argparse -import bittensor as bt -from loguru import logger - - -def check_config(cls, config: "bt.Config"): - r"""Checks/validates the config namespace object.""" - bt.logging.check_config(config) - - full_path = os.path.expanduser( - "{}/{}/{}/netuid{}/{}".format( - config.logging.logging_dir, - config.wallet.name, - config.wallet.hotkey, - config.netuid, - config.neuron.name, - ) - ) - print("full path:", full_path) - config.neuron.full_path = os.path.expanduser(full_path) - if not os.path.exists(config.neuron.full_path): - os.makedirs(config.neuron.full_path, exist_ok=True) - - if not config.neuron.dont_save_events: - # Add custom event logger for the events. - logger.level("EVENTS", no=38, icon="📝") - logger.add( - os.path.join(config.neuron.full_path, "events.log"), - rotation=config.neuron.events_retention_size, - serialize=True, - enqueue=True, - backtrace=False, - diagnose=False, - level="EVENTS", - format="{time:YYYY-MM-DD at HH:mm:ss} | {level} | {message}", - ) - - -def add_args(cls, parser): - """ - Adds relevant arguments to the parser for operation. - """ - - parser.add_argument("--netuid", type=int, help="Subnet netuid", default=1) - - parser.add_argument( - "--neuron.device", - type=str, - help="Device to run on.", - default="cuda" if torch.cuda.is_available() else "cpu", - ) - - parser.add_argument( - "--neuron.epoch_length", - type=int, - help="The default epoch length (how often we set weights, measured in 12 second blocks).", - default=100, - ) - - parser.add_argument( - "--mock", - action="store_true", - help="Mock neuron and all network components.", - default=False, - ) - - parser.add_argument( - "--neuron.events_retention_size", - type=str, - help="Events retention size.", - default="2 GB", - ) - - parser.add_argument( - "--neuron.dont_save_events", - action="store_true", - help="If set, we dont save events to a log file.", - default=False, - ) - - -def add_miner_args(cls, parser): - """Add miner specific arguments to the parser.""" - - parser.add_argument( - "--neuron.name", - type=str, - help="Trials for this neuron go in neuron.root / (wallet_cold - wallet_hot) / neuron.name. ", - default="miner", - ) - - parser.add_argument( - "--blacklist.force_validator_permit", - action="store_true", - help="If set, we will force incoming requests to have a permit.", - default=False, - ) - - parser.add_argument( - "--blacklist.allow_non_registered", - action="store_true", - help="If set, miners will accept queries from non registered entities. (Dangerous!)", - default=False, - ) - - parser.add_argument( - "--wandb.project_name", - type=str, - default="template-miners", - help="Wandb project to log to.", - ) - - parser.add_argument( - "--wandb.entity", - type=str, - default="opentensor-dev", - help="Wandb entity to log to.", - ) - - -def add_validator_args(cls, parser): - """Add validator specific arguments to the parser.""" - - parser.add_argument( - "--neuron.name", - type=str, - help="Trials for this neuron go in neuron.root / (wallet_cold - wallet_hot) / neuron.name. ", - default="validator", - ) - - parser.add_argument( - "--neuron.timeout", - type=float, - help="The timeout for each forward call in seconds.", - default=10, - ) - - parser.add_argument( - "--neuron.num_concurrent_forwards", - type=int, - help="The number of concurrent forwards running at any time.", - default=1, - ) - - parser.add_argument( - "--neuron.sample_size", - type=int, - help="The number of miners to query in a single step.", - default=50, - ) - - parser.add_argument( - "--neuron.disable_set_weights", - action="store_true", - help="Disables setting weights.", - default=False, - ) - - parser.add_argument( - "--neuron.moving_average_alpha", - type=float, - help="Moving average alpha parameter, how much to add of the new observation.", - default=0.1, - ) - - parser.add_argument( - "--neuron.axon_off", - "--axon_off", - action="store_true", - # Note: the validator needs to serve an Axon with their IP or they may - # be blacklisted by the firewall of serving peers on the network. - help="Set this flag to not attempt to serve an Axon.", - default=False, - ) - - parser.add_argument( - "--neuron.vpermit_tao_limit", - type=int, - help="The maximum number of TAO allowed to query a validator with a vpermit.", - default=4096, - ) - - parser.add_argument( - "--wandb.project_name", - type=str, - help="The name of the project where you are sending the new run.", - default="template-validators", - ) - - parser.add_argument( - "--wandb.entity", - type=str, - help="The name of the project where you are sending the new run.", - default="opentensor-dev", - ) - - -def config(cls): - """ - Returns the configuration object specific to this miner or validator after adding relevant arguments. - """ - parser = argparse.ArgumentParser() - bt.wallet.add_args(parser) - bt.subtensor.add_args(parser) - bt.logging.add_args(parser) - bt.axon.add_args(parser) - cls.add_args(parser) - return bt.config(parser) diff --git a/subnet/validator/utils/misc.py b/subnet/validator/utils/misc.py deleted file mode 100644 index 80b4e61..0000000 --- a/subnet/validator/utils/misc.py +++ /dev/null @@ -1,112 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2023 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 time -import math -import hashlib as rpccheckhealth -from math import floor -from typing import Callable, Any -from functools import lru_cache, update_wrapper - - -# LRU Cache with TTL -def ttl_cache(maxsize: int = 128, typed: bool = False, ttl: int = -1): - """ - Decorator that creates a cache of the most recently used function calls with a time-to-live (TTL) feature. - The cache evicts the least recently used entries if the cache exceeds the `maxsize` or if an entry has - been in the cache longer than the `ttl` period. - - Args: - maxsize (int): Maximum size of the cache. Once the cache grows to this size, subsequent entries - replace the least recently used ones. Defaults to 128. - typed (bool): If set to True, arguments of different types will be cached separately. For example, - f(3) and f(3.0) will be treated as distinct calls with distinct results. Defaults to False. - ttl (int): The time-to-live for each cache entry, measured in seconds. If set to a non-positive value, - the TTL is set to a very large number, effectively making the cache entries permanent. Defaults to -1. - - Returns: - Callable: A decorator that can be applied to functions to cache their return values. - - The decorator is useful for caching results of functions that are expensive to compute and are called - with the same arguments frequently within short periods of time. The TTL feature helps in ensuring - that the cached values are not stale. - - Example: - @ttl_cache(ttl=10) - def get_data(param): - # Expensive data retrieval operation - return data - """ - if ttl <= 0: - ttl = 65536 - hash_gen = _ttl_hash_gen(ttl) - - def wrapper(func: Callable) -> Callable: - @lru_cache(maxsize, typed) - def ttl_func(ttl_hash, *args, **kwargs): - return func(*args, **kwargs) - - def wrapped(*args, **kwargs) -> Any: - th = next(hash_gen) - return ttl_func(th, *args, **kwargs) - - return update_wrapper(wrapped, func) - - return wrapper - - -def _ttl_hash_gen(seconds: int): - """ - Internal generator function used by the `ttl_cache` decorator to generate a new hash value at regular - time intervals specified by `seconds`. - - Args: - seconds (int): The number of seconds after which a new hash value will be generated. - - Yields: - int: A hash value that represents the current time interval. - - This generator is used to create time-based hash values that enable the `ttl_cache` to determine - whether cached entries are still valid or if they have expired and should be recalculated. - """ - start_time = time.time() - while True: - yield floor((time.time() - start_time) / seconds) - - -# 12 seconds updating block. -@ttl_cache(maxsize=1, ttl=12) -def ttl_get_block(self) -> int: - """ - Retrieves the current block number from the blockchain. This method is cached with a time-to-live (TTL) - of 12 seconds, meaning that it will only refresh the block number from the blockchain at most every 12 seconds, - reducing the number of calls to the underlying blockchain interface. - - Returns: - int: The current block number on the blockchain. - - This method is useful for applications that need to access the current block number frequently and can - tolerate a delay of up to 12 seconds for the latest information. By using a cache with TTL, the method - efficiently reduces the workload on the blockchain interface. - - Example: - current_block = ttl_get_block(self) - - Note: self here is the miner or validator instance - """ - return self.subtensor.get_current_block() diff --git a/subnet/validator/utils/uids.py b/subnet/validator/utils/uids.py deleted file mode 100644 index c117349..0000000 --- a/subnet/validator/utils/uids.py +++ /dev/null @@ -1,64 +0,0 @@ -import torch -import random -import bittensor as bt -from typing import List - - -def check_uid_availability( - metagraph: "bt.metagraph.Metagraph", uid: int, vpermit_tao_limit: int -) -> bool: - """Check if uid is available. The UID should be available if it is serving and has less than vpermit_tao_limit stake - Args: - metagraph (:obj: bt.metagraph.Metagraph): Metagraph object - uid (int): uid to be checked - vpermit_tao_limit (int): Validator permit tao limit - Returns: - bool: True if uid is available, False otherwise - """ - # Filter non serving axons. - if not metagraph.axons[uid].is_serving: - return False - # Filter validator permit > 1024 stake. - if metagraph.validator_permit[uid]: - if metagraph.S[uid] > vpermit_tao_limit: - return False - # Available otherwise. - return True - - -def get_random_uids( - self, k: int, exclude: List[int] = None -) -> torch.LongTensor: - """Returns k available random uids from the metagraph. - Args: - k (int): Number of uids to return. - exclude (List[int]): List of uids to exclude from the random sampling. - Returns: - uids (torch.LongTensor): Randomly sampled available uids. - Notes: - If `k` is larger than the number of available `uids`, set `k` to the number of available `uids`. - """ - print('-------------------------', self.metagraph.axons[0].is_serving) - # candidate_uids = [] - # avail_uids = [] - - # for uid in range(self.metagraph.n.item()): - # uid_is_available = check_uid_availability( - # self.metagraph, uid, self.config.neuron.vpermit_tao_limit - # ) - # uid_is_not_excluded = exclude is None or uid not in exclude - - # if uid_is_available: - # avail_uids.append(uid) - # if uid_is_not_excluded: - # candidate_uids.append(uid) - - # # Check if candidate_uids contain enough for querying, if not grab all avaliable uids - # available_uids = candidate_uids - # if len(candidate_uids) < k: - # available_uids += random.sample( - # [uid for uid in avail_uids if uid not in candidate_uids], - # k - len(candidate_uids), - # ) - uids = torch.tensor([0]) - return uids diff --git a/subnet/validator/validator.py b/subnet/validator/validator.py deleted file mode 100644 index 23fff1f..0000000 --- a/subnet/validator/validator.py +++ /dev/null @@ -1,229 +0,0 @@ -import copy -import torch -import asyncio -import argparse -import threading -import bittensor as bt - -from typing import List - -from neuron import BaseNeuron -from mock import MockDendrite -from utils.config import add_validator_args -from miner_manager import MinerManager - - -class BaseValidatorNeuron(BaseNeuron): - """ - Base class for Bittensor validators. Your validator should inherit from this class. - """ - - neuron_type: str = "ValidatorNeuron" - - @classmethod - def add_args(cls, parser: argparse.ArgumentParser): - super().add_args(parser) - add_validator_args(cls, parser) - - def __init__(self, config=None): - super().__init__(config=config) - # Save a copy of the hotkeys to local memory. - if self.subtensor_connected: - self.hotkeys = copy.deepcopy(self.metagraph.hotkeys) - - # Set up initial scoring weights for validation - bt.logging.info("Building validation weights.") - self.scores = torch.zeros( - self.metagraph.n, dtype=torch.float32, device=self.device - ) - - # Init sync with the network. Updates the metagraph. - self.sync() - - # Create asyncio event loop to manage async tasks. - self.loop = asyncio.get_event_loop() - # Instantiate runners - self.should_exit: bool = False - self.is_running: bool = False - self.thread: threading.Thread = None - self.lock = asyncio.Lock() - - def run_in_background_thread(self): - """ - Starts the validator's operations in a background thread upon entering the context. - This method facilitates the use of the validator in a 'with' statement. - """ - if not self.is_running: - bt.logging.debug("Starting validator in background thread.") - self.should_exit = False - self.thread = threading.Thread(target=self.run, daemon=True) - self.thread.start() - self.is_running = True - bt.logging.debug("Started") - - def stop_run_thread(self): - """ - Stops the validator's operations that are running in the background thread. - """ - if self.is_running: - bt.logging.debug("Stopping validator in background thread.") - self.should_exit = True - self.thread.join(5) - self.is_running = False - bt.logging.debug("Stopped") - - def __enter__(self): - self.run_in_background_thread() - return self - - def __exit__(self, exc_type, exc_value, traceback): - if self.is_running: - bt.logging.debug("Stopping validator in background thread.") - self.should_exit = True - self.thread.join(5) - self.is_running = False - bt.logging.debug("Stopped") - - def set_weights(self): - """ - Sets the validator weights to the metagraph hotkeys based on the scores it has received from the miners. The weights determine the trust and incentive level the validator assigns to miner nodes on the network. - """ - - # Check if self.scores contains any NaN values and log a warning if it does. - if torch.isnan(self.scores).any(): - bt.logging.warning( - f"Scores contain NaN values. This may be due to a lack of responses from miners, or a bug in your reward functions." - ) - - # Calculate the average reward for each uid across non-zero values. - # Replace any NaN values with 0. - raw_weights = torch.nn.functional.normalize(self.scores, p=1, dim=0) - - bt.logging.debug("raw_weights", raw_weights) - bt.logging.debug("raw_weight_uids", self.metagraph.uids.to("cpu")) - # Process the raw weights to final_weights via subtensor limitations. - ( - processed_weight_uids, - processed_weights, - ) = bt.utils.weight_utils.process_weights_for_netuid( - uids=self.metagraph.uids.to("cpu"), - weights=raw_weights.to("cpu"), - netuid=self.config.netuid, - subtensor=self.subtensor, - metagraph=self.metagraph, - ) - bt.logging.debug("processed_weights", processed_weights) - bt.logging.debug("processed_weight_uids", processed_weight_uids) - - # Convert to uint16 weights and uids. - ( - uint_uids, - uint_weights, - ) = bt.utils.weight_utils.convert_weights_and_uids_for_emit( - uids=processed_weight_uids, weights=processed_weights - ) - bt.logging.debug("uint_weights", uint_weights) - bt.logging.debug("uint_uids", uint_uids) - - # Set the weights on chain via our subtensor connection. - result = self.subtensor.set_weights( - wallet=self.wallet, - netuid=self.config.netuid, - uids=uint_uids, - weights=uint_weights, - wait_for_finalization=False, - wait_for_inclusion=False, - version_key=self.spec_version, - ) - if result is True: - bt.logging.info("set_weights on chain successfully!") - else: - bt.logging.error("set_weights failed") - - def resync_metagraph(self): - """Resyncs the metagraph and updates the hotkeys and moving averages based on the new metagraph.""" - bt.logging.info("resync_metagraph()") - - # Copies state of metagraph before syncing. - previous_metagraph = copy.deepcopy(self.metagraph) - - # Sync the metagraph. - self.metagraph.sync(subtensor=self.subtensor) - - print(self.metagraph.last_update[self.uid]) - - bt.logging.info( - "Metagraph updated, re-syncing hotkeys, dendrite pool and moving averages" - ) - # Zero out all hotkeys that have been replaced. - for uid, hotkey in enumerate(self.hotkeys): - if hotkey != self.metagraph.hotkeys[uid]: - self.scores[uid] = 0 # hotkey has been replaced - - # Check to see if the metagraph has changed size. - # If so, we need to add new hotkeys and moving averages. - if len(self.hotkeys) < len(self.metagraph.hotkeys): - # Update the size of the moving average scores. - new_moving_average = torch.zeros((self.metagraph.n)).to( - self.device - ) - min_len = min(len(self.hotkeys), len(self.scores)) - new_moving_average[:min_len] = self.scores[:min_len] - self.scores = new_moving_average - - # Update the hotkeys. - self.hotkeys = copy.deepcopy(self.metagraph.hotkeys) - - def update_scores(self, rewards: torch.FloatTensor, uids: List[int]): - """Performs exponential moving average on the scores based on the rewards received from the miners.""" - - # Check if rewards contains NaN values. - if torch.isnan(rewards).any(): - bt.logging.warning(f"NaN values detected in rewards: {rewards}") - # Replace any NaN values in rewards with 0. - rewards = torch.nan_to_num(rewards, 0) - - # Check if `uids` is already a tensor and clone it to avoid the warning. - if isinstance(uids, torch.Tensor): - uids_tensor = uids.clone().detach() - else: - uids_tensor = torch.tensor(uids).to(self.device) - - # Compute forward pass rewards, assumes uids are mutually exclusive. - # shape: [ metagraph.n ] - scattered_rewards: torch.FloatTensor = self.scores.scatter( - 0, uids_tensor, rewards - ).to(self.device) - bt.logging.debug(f"Scattered rewards: {rewards}") - - # Update scores with rewards produced by this step. - # shape: [ metagraph.n ] - alpha: float = self.config.neuron.moving_average_alpha - self.scores: torch.FloatTensor = alpha * scattered_rewards + ( - 1 - alpha - ) * self.scores.to(self.device) - bt.logging.debug(f"Updated moving avg scores: {self.scores}") - - def save_state(self): - """Saves the state of the validator to a file.""" - bt.logging.info("Saving validator state.") - - # Save the state of the validator to file. - torch.save( - { - "step": self.step, - "scores": self.scores, - "hotkeys": self.hotkeys, - }, - self.config.neuron.full_path + "/state.pt", - ) - - def load_state(self): - """Loads the state of the validator from a file.""" - bt.logging.info("Loading validator state.") - - # Load the state of the validator from file. - state = torch.load(self.config.neuron.full_path + "/state.pt") - self.step = state["step"] - self.scores = state["scores"] - self.hotkeys = state["hotkeys"]