From 19a052a442279de168c56855c5bae698e6f47c1b Mon Sep 17 00:00:00 2001 From: bkb2135 Date: Thu, 6 Jun 2024 19:02:13 +0000 Subject: [PATCH 01/16] initial push on openai miner --- prompting/base/prompting_miner.py | 21 ++-- prompting/miners/__init__.py | 3 +- prompting/miners/langchain_miner.py | 168 ++++++++++++++++++++++++++++ prompting/miners/openai_miner.py | 72 ++++++------ 4 files changed, 219 insertions(+), 45 deletions(-) create mode 100644 prompting/miners/langchain_miner.py diff --git a/prompting/base/prompting_miner.py b/prompting/base/prompting_miner.py index f6b188185..8c0111139 100644 --- a/prompting/base/prompting_miner.py +++ b/prompting/base/prompting_miner.py @@ -23,7 +23,10 @@ from prompting.protocol import StreamPromptingSynapse from prompting.base.miner import BaseStreamMinerNeuron from datetime import datetime +from typing import List, Dict +# Define the type for a list of dictionaries +ListOfDicts = List[Dict[str, str]] class BaseStreamPromptingMiner(BaseStreamMinerNeuron): """ @@ -160,21 +163,25 @@ def init_wandb(self): def log_event( self, timing: float, - prompt: str, - completion: str, - system_prompt: str, + messages: ListOfDicts, + accumulated_chunks: List[str] = [], + accumulated_chunks_timings: List[float] = [], extra_info: dict = {}, ): if not getattr(self, "wandb_run", None): self.init_wandb() + + uid = self.metagraph.hotkeys.index(self.wallet.hotkey.ss58_address) step_log = { "epoch_time": timing, # "block": self.last_epoch_block, - "prompt": prompt, - "completion": completion, - "system_prompt": system_prompt, - "uid": self.metagraph.hotkeys.index(self.wallet.hotkey.ss58_address), + "messages": messages, + "accumulated_chunks": accumulated_chunks, + "accumulated_chunks_timings": accumulated_chunks_timings, + "uid": uid, + "coldkey": self.metagraph.coldkeys[uid], + "hotkey": self.metagraph.hotkeys[uid], "stake": self.metagraph.S[self.uid].item(), "trust": self.metagraph.T[self.uid].item(), "incentive": self.metagraph.I[self.uid].item(), diff --git a/prompting/miners/__init__.py b/prompting/miners/__init__.py index 9c3a34bf2..01bfeef3a 100644 --- a/prompting/miners/__init__.py +++ b/prompting/miners/__init__.py @@ -5,4 +5,5 @@ # Real miners from .hf_miner import HuggingFaceMiner -from .openai_miner import OpenAIMiner +from .langchain_miner import LangchainMiner +from .openai_miner import OpenAIMiner \ No newline at end of file diff --git a/prompting/miners/langchain_miner.py b/prompting/miners/langchain_miner.py new file mode 100644 index 000000000..395cb6ce4 --- /dev/null +++ b/prompting/miners/langchain_miner.py @@ -0,0 +1,168 @@ +# The MIT License (MIT) +# Copyright © 2024 Yuma Rao + +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the “Software”), to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of +# the Software. + +# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. + +import time +import os +import bittensor as bt +import argparse +from starlette.types import Send +from functools import partial +from typing import Dict, Awaitable + +# Bittensor Miner Template: +from prompting.base.prompting_miner import BaseStreamPromptingMiner +from prompting.protocol import StreamPromptingSynapse + +# import base miner class which takes care of most of the boilerplate + +from prompting.miners.utils import OpenAIUtils + +from langchain.prompts import ChatPromptTemplate +from langchain_core.output_parsers import StrOutputParser +from langchain.chat_models import ChatOpenAI +from dotenv import load_dotenv, find_dotenv +from langchain_core.runnables.base import RunnableSequence + + +class LangchainMiner(BaseStreamPromptingMiner, OpenAIUtils): + """Langchain-based miner which uses OpenAI's API as the LLM. + This miner does not use any tools or external APIs when processing requests - it relies entirely on the models' own representation and world model. In some cases, this can produce lower quality results. + You should also install the dependencies for this miner, which can be found in the requirements.txt file in this directory. + """ + + @classmethod + def add_args(cls, parser: argparse.ArgumentParser): + """ + Adds OpenAI-specific arguments to the command line parser. + """ + super().add_args(parser) + + def __init__(self, config=None): + super().__init__(config=config) + + bt.logging.info(f"Initializing with model {self.config.neuron.model_id}...") + + if self.config.wandb.on: + self.identity_tags = ("openai_miner",) + (self.config.neuron.model_id,) + + _ = load_dotenv(find_dotenv()) + api_key = os.environ.get("OPENAI_API_KEY") + + # Set openai key and other args + self.model = ChatOpenAI( + api_key=api_key, + model_name=self.config.neuron.model_id, + max_tokens=self.config.neuron.max_tokens, + temperature=self.config.neuron.temperature, + ) + + self.system_prompt = self.config.neuron.system_prompt + self.accumulated_total_tokens = 0 + self.accumulated_prompt_tokens = 0 + self.accumulated_completion_tokens = 0 + self.accumulated_total_cost = 0 + + def forward(self, synapse: StreamPromptingSynapse) -> Awaitable: + async def _forward( + self, + message: str, + init_time: float, + timeout_threshold: float, + chain: RunnableSequence, + chain_formatter: Dict[str, str], + send: Send, + ): + buffer = [] + temp_completion = "" # for wandb logging + timeout_reached = False + + try: + # Langchain built in streaming. 'astream' also available for async + for token in chain.stream(chain_formatter): + buffer.append(token) + + if time.time() - init_time > timeout_threshold: + bt.logging.debug(f"⏰ Timeout reached, stopping streaming") + timeout_reached = True + break + + if len(buffer) == self.config.neuron.streaming_batch_size: + joined_buffer = "".join(buffer) + temp_completion += joined_buffer + bt.logging.debug(f"Streamed tokens: {joined_buffer}") + + await send( + { + "type": "http.response.body", + "body": joined_buffer.encode("utf-8"), + "more_body": True, + } + ) + buffer = [] + + if ( + buffer and not timeout_reached + ): # Don't send the last buffer of data if timeout. + joined_buffer = "".join(buffer) + await send( + { + "type": "http.response.body", + "body": joined_buffer.encode("utf-8"), + "more_body": False, + } + ) + + except Exception as e: + bt.logging.error(f"Error in forward: {e}") + if self.config.neuron.stop_on_forward_exception: + self.should_exit = True + + finally: + synapse_latency = time.time() - init_time + if self.config.wandb.on: + self.log_event( + timing=synapse_latency, + prompt=message, + completion=temp_completion, + system_prompt=self.system_prompt, + ) + + bt.logging.debug(f"📧 Message received, forwarding synapse: {synapse}") + + prompt = ChatPromptTemplate.from_messages( + [("system", self.system_prompt), ("user", "{input}")] + ) + chain = prompt | self.model | StrOutputParser() + + role = synapse.roles[-1] + message = synapse.messages[-1] + + chain_formatter = {"role": role, "input": message} + + init_time = time.time() + timeout_threshold = synapse.timeout + + token_streamer = partial( + _forward, + self, + message, + init_time, + timeout_threshold, + chain, + chain_formatter, + ) + return synapse.create_streaming_response(token_streamer) diff --git a/prompting/miners/openai_miner.py b/prompting/miners/openai_miner.py index 73bf17182..71d3c1e3d 100644 --- a/prompting/miners/openai_miner.py +++ b/prompting/miners/openai_miner.py @@ -30,12 +30,11 @@ # import base miner class which takes care of most of the boilerplate from prompting.miners.utils import OpenAIUtils - -from langchain.prompts import ChatPromptTemplate -from langchain_core.output_parsers import StrOutputParser -from langchain.chat_models import ChatOpenAI from dotenv import load_dotenv, find_dotenv -from langchain_core.runnables.base import RunnableSequence +from openai import OpenAI +from typing import List, Dict + +# Define the type for a list of dictionaries class OpenAIMiner(BaseStreamPromptingMiner, OpenAIUtils): @@ -63,13 +62,8 @@ def __init__(self, config=None): api_key = os.environ.get("OPENAI_API_KEY") # Set openai key and other args - self.model = ChatOpenAI( - api_key=api_key, - model_name=self.config.neuron.model_id, - max_tokens=self.config.neuron.max_tokens, - temperature=self.config.neuron.temperature, - ) - + self.model = OpenAI(api_key=api_key) + self.system_prompt = self.config.neuron.system_prompt self.accumulated_total_tokens = 0 self.accumulated_prompt_tokens = 0 @@ -79,21 +73,37 @@ def __init__(self, config=None): def forward(self, synapse: StreamPromptingSynapse) -> Awaitable: async def _forward( self, - message: str, + synapse: StreamPromptingSynapse, init_time: float, timeout_threshold: float, - chain: RunnableSequence, - chain_formatter: Dict[str, str], send: Send, ): buffer = [] + accumulated_chunks = [] + accumulated_chunks_timings = [] temp_completion = "" # for wandb logging timeout_reached = False - try: - # Langchain built in streaming. 'astream' also available for async - for token in chain.stream(chain_formatter): - buffer.append(token) + try: + system_prompt_message = { 'role': 'system', 'content': self.system_prompt } + synapse_messages = {message.role: message.content for message in synapse.messages} + + messages = [system_prompt_message, synapse_messages] + + start_time = time.time() + stream_response = self.model.chat.completions.create( + model=self.config.neuron.model_id, + messages=messages, + temperature=self.config.neuron.temperature, + max_tokens=self.config.neuron.max_tokens, + stream=True + ) + + for chunk in stream_response: + chunk_content = chunk.choices[0].delta.content + accumulated_chunks.append(chunk_content) + accumulated_chunks_timings.append(time.time() - start_time) + buffer.append(chunk_content) if time.time() - init_time > timeout_threshold: bt.logging.debug(f"⏰ Timeout reached, stopping streaming") @@ -136,33 +146,21 @@ async def _forward( if self.config.wandb.on: self.log_event( timing=synapse_latency, - prompt=message, - completion=temp_completion, - system_prompt=self.system_prompt, + messages=messages, + accumulated_chunks=accumulated_chunks, + accumulated_chunks_timings = accumulated_chunks_timings, ) bt.logging.debug(f"📧 Message received, forwarding synapse: {synapse}") - - prompt = ChatPromptTemplate.from_messages( - [("system", self.system_prompt), ("user", "{input}")] - ) - chain = prompt | self.model | StrOutputParser() - - role = synapse.roles[-1] - message = synapse.messages[-1] - - chain_formatter = {"role": role, "input": message} - + init_time = time.time() timeout_threshold = synapse.timeout token_streamer = partial( _forward, self, - message, + synapse, init_time, - timeout_threshold, - chain, - chain_formatter, + timeout_threshold, ) return synapse.create_streaming_response(token_streamer) From 037ebfc9e0e9601e65b7fd1aa346b7051b45eeb9 Mon Sep 17 00:00:00 2001 From: bkb2135 Date: Thu, 6 Jun 2024 20:56:16 +0000 Subject: [PATCH 02/16] deprecates langchain and hf miner, adds openai miner --- neurons/miners/huggingface/miner.py | 9 ++++++-- prompting/base/prompting_miner.py | 33 ++++++++++++++++------------- prompting/miners/__init__.py | 2 -- prompting/miners/hf_miner.py | 3 ++- prompting/miners/langchain_miner.py | 3 ++- prompting/miners/openai_miner.py | 23 ++++++++++++++------ 6 files changed, 46 insertions(+), 27 deletions(-) diff --git a/neurons/miners/huggingface/miner.py b/neurons/miners/huggingface/miner.py index 29eaa6405..6c33730d1 100644 --- a/neurons/miners/huggingface/miner.py +++ b/neurons/miners/huggingface/miner.py @@ -17,10 +17,11 @@ import time import bittensor as bt from prompting.miners import HuggingFaceMiner +from deprecated import deprecated -# This is the main function, which runs the miner. -if __name__ == "__main__": +@deprecated(version="2.4.1+", reason="Class is deprecated, use openai miner for reference on example miner.") +def main(): with HuggingFaceMiner() as miner: while True: miner.log_status() @@ -29,3 +30,7 @@ if miner.should_exit: bt.logging.warning("Ending miner...") break + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/prompting/base/prompting_miner.py b/prompting/base/prompting_miner.py index 8c0111139..e7b760501 100644 --- a/prompting/base/prompting_miner.py +++ b/prompting/base/prompting_miner.py @@ -25,9 +25,6 @@ from datetime import datetime from typing import List, Dict -# Define the type for a list of dictionaries -ListOfDicts = List[Dict[str, str]] - class BaseStreamPromptingMiner(BaseStreamMinerNeuron): """ Your miner neuron class. You should use this class to define your miner's behavior. @@ -162,31 +159,37 @@ def init_wandb(self): def log_event( self, + synapse: StreamPromptingSynapse, timing: float, - messages: ListOfDicts, + messages, accumulated_chunks: List[str] = [], accumulated_chunks_timings: List[float] = [], extra_info: dict = {}, ): if not getattr(self, "wandb_run", None): self.init_wandb() - - uid = self.metagraph.hotkeys.index(self.wallet.hotkey.ss58_address) - + + dendrite_uid = self.metagraph.hotkeys.index(synapse.dendrite.hotkey) step_log = { "epoch_time": timing, # "block": self.last_epoch_block, "messages": messages, "accumulated_chunks": accumulated_chunks, "accumulated_chunks_timings": accumulated_chunks_timings, - "uid": uid, - "coldkey": self.metagraph.coldkeys[uid], - "hotkey": self.metagraph.hotkeys[uid], - "stake": self.metagraph.S[self.uid].item(), - "trust": self.metagraph.T[self.uid].item(), - "incentive": self.metagraph.I[self.uid].item(), - "consensus": self.metagraph.C[self.uid].item(), - "dividends": self.metagraph.D[self.uid].item(), + "validator_uid": dendrite_uid, + "validator_ip": synapse.dendrite.ip, + "validator_coldkey": self.metagraph.coldkeys[dendrite_uid], + "validator_hotkey": self.metagraph.hotkeys[dendrite_uid], + "validator_stake": self.metagraph.S[dendrite_uid].item(), + "validator_trust": self.metagraph.T[dendrite_uid].item(), + "validator_incentive": self.metagraph.I[dendrite_uid].item(), + "validator_consensus": self.metagraph.C[dendrite_uid].item(), + "validator_dividends": self.metagraph.D[dendrite_uid].item(), + "miner_stake": self.metagraph.S[self.uid].item(), + "miner_trust": self.metagraph.T[self.uid].item(), + "miner_incentive": self.metagraph.I[self.uid].item(), + "miner_consensus": self.metagraph.C[self.uid].item(), + "miner_dividends": self.metagraph.D[self.uid].item(), **extra_info, } diff --git a/prompting/miners/__init__.py b/prompting/miners/__init__.py index 01bfeef3a..f53f9bb42 100644 --- a/prompting/miners/__init__.py +++ b/prompting/miners/__init__.py @@ -4,6 +4,4 @@ from .phrase import PhraseMiner # Real miners -from .hf_miner import HuggingFaceMiner -from .langchain_miner import LangchainMiner from .openai_miner import OpenAIMiner \ No newline at end of file diff --git a/prompting/miners/hf_miner.py b/prompting/miners/hf_miner.py index 021361d91..659035fda 100644 --- a/prompting/miners/hf_miner.py +++ b/prompting/miners/hf_miner.py @@ -28,8 +28,9 @@ # import base miner class which takes care of most of the boilerplate from prompting.base.prompting_miner import BaseStreamPromptingMiner +from deprecated import deprecated - +@deprecated(version="2.4.1+", reason="Class is deprecated, use openai miner for reference on example miner.") class HuggingFaceMiner(BaseStreamPromptingMiner): """ Base miner which runs zephyr (https://huggingface.co/HuggingFaceH4/zephyr-7b-beta) diff --git a/prompting/miners/langchain_miner.py b/prompting/miners/langchain_miner.py index 395cb6ce4..b77d1433e 100644 --- a/prompting/miners/langchain_miner.py +++ b/prompting/miners/langchain_miner.py @@ -36,8 +36,9 @@ from langchain.chat_models import ChatOpenAI from dotenv import load_dotenv, find_dotenv from langchain_core.runnables.base import RunnableSequence +from deprecated import deprecated - +@deprecated(version="2.4.1+", reason="Class is deprecated, use openai miner for reference on example miner.") class LangchainMiner(BaseStreamPromptingMiner, OpenAIUtils): """Langchain-based miner which uses OpenAI's API as the LLM. This miner does not use any tools or external APIs when processing requests - it relies entirely on the models' own representation and world model. In some cases, this can produce lower quality results. diff --git a/prompting/miners/openai_miner.py b/prompting/miners/openai_miner.py index 71d3c1e3d..9afe0ea2d 100644 --- a/prompting/miners/openai_miner.py +++ b/prompting/miners/openai_miner.py @@ -33,6 +33,7 @@ from dotenv import load_dotenv, find_dotenv from openai import OpenAI from typing import List, Dict +from traceback import print_exception # Define the type for a list of dictionaries @@ -81,14 +82,16 @@ async def _forward( buffer = [] accumulated_chunks = [] accumulated_chunks_timings = [] + messages = [] temp_completion = "" # for wandb logging timeout_reached = False + try: - system_prompt_message = { 'role': 'system', 'content': self.system_prompt } - synapse_messages = {message.role: message.content for message in synapse.messages} + system_prompt_message = [{ 'role': 'system', 'content': self.system_prompt }] + synapse_messages = [{'role': role, 'content': message} for role, message in zip(synapse.roles, synapse.messages)] - messages = [system_prompt_message, synapse_messages] + messages = system_prompt_message + synapse_messages start_time = time.time() stream_response = self.model.chat.completions.create( @@ -101,9 +104,15 @@ async def _forward( for chunk in stream_response: chunk_content = chunk.choices[0].delta.content + + if chunk_content is None: + bt.logging.info("OpenAI returned chunk content with None") + continue + accumulated_chunks.append(chunk_content) - accumulated_chunks_timings.append(time.time() - start_time) - buffer.append(chunk_content) + accumulated_chunks_timings.append(time.time() - start_time) + + buffer.append(chunk_content) if time.time() - init_time > timeout_threshold: bt.logging.debug(f"⏰ Timeout reached, stopping streaming") @@ -138,6 +147,7 @@ async def _forward( except Exception as e: bt.logging.error(f"Error in forward: {e}") + bt.logging.error(print_exception(type(e), e, e.__traceback__)) if self.config.neuron.stop_on_forward_exception: self.should_exit = True @@ -145,13 +155,14 @@ async def _forward( synapse_latency = time.time() - init_time if self.config.wandb.on: self.log_event( + synapse=synapse, timing=synapse_latency, messages=messages, accumulated_chunks=accumulated_chunks, accumulated_chunks_timings = accumulated_chunks_timings, ) - bt.logging.debug(f"📧 Message received, forwarding synapse: {synapse}") + bt.logging.debug(f"📧 Message received from {synapse.dendrite.hotkey}, IP: {synapse.dendrite.ip}; \nForwarding synapse: {synapse}") init_time = time.time() timeout_threshold = synapse.timeout From 0d7a57668826d89c3453316843482fbefa2b4433 Mon Sep 17 00:00:00 2001 From: p-ferreira <38992619+p-ferreira@users.noreply.github.com> Date: Tue, 11 Jun 2024 12:13:28 -0400 Subject: [PATCH 03/16] brings back pm2 autorun command --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index f78fc33a1..75c85a466 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,15 @@ Example of running a Llama3 miner: pm2 start neurons/miners/huggingface/miner.py --interpreter python3 --name llama3_miner -- --netuid 1 --subtensor.network finney --wallet.name my_wallet --wallet.hotkey m1 --neuron.model_id casperhansen/llama-3-70b-instruct-awq --neuron.load_in_4bit True --axon.port 21988 --logging.debug ``` +## Running with autoupdate + +You can run the validator in auto-update mode by using pm2 along with the `run.sh` bash script. This command will initiate two pm2 processes: one for auto-update monitoring, named **s1_validator_update**, and another for running the validator itself, named **s1_validator_main_process**. +```bash +pm2 start run.sh --name s1_validator_autoupdate -- --wallet.name --wallet.hotkey +``` + +> Note: this is not an end solution, major releases or changes in requirements will still require you to manually restart the processes. Regularly monitor the health of your validator to ensure optimal performance. + # Testnet We highly recommend that you run your miners on testnet before deploying on main. This is give you an opportunity to debug your systems, and ensure that you will not lose valuable immunity time. The SN1 testnet is **netuid 61**. From 33819af0dc46a37fd74d06d9ca970538e4487af6 Mon Sep 17 00:00:00 2001 From: bkb2135 Date: Tue, 11 Jun 2024 23:14:39 +0000 Subject: [PATCH 04/16] Prevent multi-turn on translation and sentiment --- prompting/forward.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/prompting/forward.py b/prompting/forward.py index c6f0ad268..958083288 100644 --- a/prompting/forward.py +++ b/prompting/forward.py @@ -321,6 +321,10 @@ async def forward(self): if random.random()<0.5 or turn>=1: break + single_turn_tasks = ['sentiment', 'translation'] + if task.name in single_turn_tasks: + break + history = '\n'.join([f"{role}: {message}" for role, message in zip(roles, messages)]) # Use PREVIOUS task context From a91bbf0df83ae5ac227f6787cbf15037138b035d Mon Sep 17 00:00:00 2001 From: p-ferreira Date: Wed, 12 Jun 2024 16:00:30 +0000 Subject: [PATCH 05/16] updates gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 5dfe55187..e05499bf2 100644 --- a/.gitignore +++ b/.gitignore @@ -163,6 +163,9 @@ cython_debug/ #.idea/ testing/ +data/* +plots/* +notebooks/* core app.config.js wandb From 306394b9598e29fb807b3dbb884bf34827757c16 Mon Sep 17 00:00:00 2001 From: p-ferreira Date: Wed, 12 Jun 2024 16:19:15 +0000 Subject: [PATCH 06/16] bumps bittensor version --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 7d11d95fc..79e0965cf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ angle_emb==0.4.4 -bittensor==7.0.1 +bittensor==7.1.1 bs4==0.0.2 click==8.1.3 datasets==2.14.6 From 2685ab92b5590048ae34cb0a51aa1ff2be6fb0c1 Mon Sep 17 00:00:00 2001 From: p-ferreira Date: Wed, 12 Jun 2024 16:23:47 +0000 Subject: [PATCH 07/16] adds block to logged wandb data --- prompting/base/prompting_miner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prompting/base/prompting_miner.py b/prompting/base/prompting_miner.py index e7b760501..3630ec75b 100644 --- a/prompting/base/prompting_miner.py +++ b/prompting/base/prompting_miner.py @@ -172,7 +172,7 @@ def log_event( dendrite_uid = self.metagraph.hotkeys.index(synapse.dendrite.hotkey) step_log = { "epoch_time": timing, - # "block": self.last_epoch_block, + "block": self.last_epoch_block, "messages": messages, "accumulated_chunks": accumulated_chunks, "accumulated_chunks_timings": accumulated_chunks_timings, From 2a3cbd592d085a27259efee13329aef3444ffd82 Mon Sep 17 00:00:00 2001 From: p-ferreira Date: Wed, 12 Jun 2024 16:29:10 +0000 Subject: [PATCH 08/16] updates version --- prompting/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prompting/__init__.py b/prompting/__init__.py index 65aed54ea..511c0c7b6 100644 --- a/prompting/__init__.py +++ b/prompting/__init__.py @@ -16,7 +16,7 @@ # DEALINGS IN THE SOFTWARE. # Define the version of the template module. -__version__ = "2.4.2" +__version__ = "2.5.0" version_split = __version__.split(".") __spec_version__ = ( (10000 * int(version_split[0])) From 231e211654b2bb83f7789ac890d6184bb9324ce4 Mon Sep 17 00:00:00 2001 From: bkb2135 Date: Wed, 12 Jun 2024 19:03:03 +0000 Subject: [PATCH 09/16] Add Unit Tests --- prompting/forward.py | 5 +++-- tests/test_forward.py | 7 ++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/prompting/forward.py b/prompting/forward.py index 958083288..a3a63272c 100644 --- a/prompting/forward.py +++ b/prompting/forward.py @@ -36,6 +36,8 @@ from prompting.utils.uids import get_random_uids from dataclasses import dataclass +SINGLE_TURN_TASKS = ['sentiment', 'translation'] + @async_log async def generate_reference(agent): loop = asyncio.get_running_loop() @@ -321,8 +323,7 @@ async def forward(self): if random.random()<0.5 or turn>=1: break - single_turn_tasks = ['sentiment', 'translation'] - if task.name in single_turn_tasks: + if task.name in SINGLE_TURN_TASKS: break history = '\n'.join([f"{role}: {message}" for role, message in zip(roles, messages)]) diff --git a/tests/test_forward.py b/tests/test_forward.py index aa826a179..36d934955 100644 --- a/tests/test_forward.py +++ b/tests/test_forward.py @@ -2,12 +2,13 @@ import asyncio import sys from functools import partial -from prompting.forward import run_step +from prompting.forward import run_step, SINGLE_TURN_TASKS from neurons.validator import Validator from prompting.tasks import QuestionAnsweringTask from .fixtures.task import WIKI_CONTEXT from prompting.agent import HumanAgent from prompting.protocol import StreamPromptingSynapse +from prompting.tasks import TASKS sys.argv = [__file__, "--mock", "--wandb.off", "--neuron.tasks", "qa"] mock_neuron = Validator() @@ -59,3 +60,7 @@ def test_generate_reference_parallel_to_dendrite( assert network_and_reference_gen_time == pytest.approx( expected_forward_time, abs=1#0.1 ) + +def test_single_turn_tasks_in_tasks(): + # Test that SINGLE_TURN_TASKS is a subset of TASKS.keys() + assert set(SINGLE_TURN_TASKS).issubset(set(TASKS.keys())) From 8d616cc4aee937cf9a4fc1e1e366468548fd106e Mon Sep 17 00:00:00 2001 From: bkb2135 Date: Thu, 13 Jun 2024 20:36:28 +0000 Subject: [PATCH 10/16] Improve multi-turn prompting --- prompting/tasks/qa.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/prompting/tasks/qa.py b/prompting/tasks/qa.py index 2fa293087..ef8bf2bab 100644 --- a/prompting/tasks/qa.py +++ b/prompting/tasks/qa.py @@ -22,9 +22,9 @@ # Used to obtain the query (which is a followup question about the context) # TODO: we may not need the entire conversation history - we can sample a subset of it (first k messages, last k messages, etc.) FOLLOWUP_PROMPT_TEMPLATE = """ -Compose a single, specific question to continue the dialogue below. Adopt the persona of the original user, including their communication style and objectives. The question should be based on the previous exchanges and must not be answerable with a simple yes or no. +Compose a single, specific question to continue the dialogue below. Adopt the persona of the original user, reflecting their communication style and objectives. The question should be rooted in the previous exchanges and should not be answerable with a simple yes or no. -The question should require detailed knowledge of the conversation history for a correct response, emphasizing requests for clarification or additional details (e.g., 'What specific steps did you take?' or 'How did that situation resolve?'). Avoid referring to the subject by name and instead use indirect pronouns or descriptions (e.g., 'he,' 'she,' 'it'). Avoid answering the question yourself and refrain from providing new information not already discussed. +Ensure the question requires detailed knowledge of the conversation history for a correct response, focusing on requests for clarification or additional details (e.g., 'What specific steps did you take?', 'Are you sure?', 'How do you know that is true', or 'How did that situation resolve?'). Use indirect pronouns or descriptions to refer to subjects (e.g., 'he,' 'she,' 'it') instead of their names. Avoid answering the question yourself and do not introduce new information not already discussed. # Context: {context} @@ -33,6 +33,7 @@ {history} """ + # Used to obtain reference answer REFERENCE_PROMPT_TEMPLATE = """\ Answer the question you will receive in detail, utilizing the following context. @@ -45,18 +46,20 @@ """ # TODO: We also need a special followup reference prompt (or just merge both) -# Used to obtain reference answer +# TODO: We should FOLLOWUP_REFERENCE_PROMPT_TEMPLATE = """\ -Answer the question you will receive in detail, utilizing the following context and conversation history as required. - -#Context: -{context} +You are a helpful assistant. Answer the question below in detail, prioritizing the use of the provided conversation history. The context is available for additional information if needed, but it may not always be relevant. # Conversation History: {history} +# Context (optional): +{context} + # Question: {question} + +Ensure your answer references relevant parts of the conversation history. Use the context only if it provides additional necessary information. """ @dataclass From 3a3fab372c5451050b40dbb62f6392f338383aaa Mon Sep 17 00:00:00 2001 From: bkb2135 Date: Thu, 13 Jun 2024 22:01:43 +0000 Subject: [PATCH 11/16] Reiterate need for pronouns --- prompting/tasks/qa.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/prompting/tasks/qa.py b/prompting/tasks/qa.py index ef8bf2bab..9f82cf840 100644 --- a/prompting/tasks/qa.py +++ b/prompting/tasks/qa.py @@ -24,8 +24,9 @@ FOLLOWUP_PROMPT_TEMPLATE = """ Compose a single, specific question to continue the dialogue below. Adopt the persona of the original user, reflecting their communication style and objectives. The question should be rooted in the previous exchanges and should not be answerable with a simple yes or no. -Ensure the question requires detailed knowledge of the conversation history for a correct response, focusing on requests for clarification or additional details (e.g., 'What specific steps did you take?', 'Are you sure?', 'How do you know that is true', or 'How did that situation resolve?'). Use indirect pronouns or descriptions to refer to subjects (e.g., 'he,' 'she,' 'it') instead of their names. Avoid answering the question yourself and do not introduce new information not already discussed. +Ensure the question requires detailed knowledge of the conversation history for a correct response, focusing on requests for clarification or additional details (e.g., 'What specific steps did you take?', 'Are you sure?', 'How do you know that is true', or 'How did that situation resolve?'). Use indirect pronouns or descriptions to refer to subjects instead of their names. Avoid answering the question yourself and do not introduce new information not already discussed. +When asking a followup question, you should use pronouns or descriptions to refer to subjects instead of their names. # Context: {context} From 3576010c246d1e77ce5d63221d9b75811a8bb9d2 Mon Sep 17 00:00:00 2001 From: bkb2135 Date: Thu, 13 Jun 2024 23:30:54 +0000 Subject: [PATCH 12/16] Stress importance of pronouns --- prompting/tasks/qa.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prompting/tasks/qa.py b/prompting/tasks/qa.py index 9f82cf840..133754ddd 100644 --- a/prompting/tasks/qa.py +++ b/prompting/tasks/qa.py @@ -26,7 +26,7 @@ Ensure the question requires detailed knowledge of the conversation history for a correct response, focusing on requests for clarification or additional details (e.g., 'What specific steps did you take?', 'Are you sure?', 'How do you know that is true', or 'How did that situation resolve?'). Use indirect pronouns or descriptions to refer to subjects instead of their names. Avoid answering the question yourself and do not introduce new information not already discussed. -When asking a followup question, you should use pronouns or descriptions to refer to subjects instead of their names. +When asking a followup question, you should use pronouns or descriptions to refer to subjects instead of their names. You absolutely must not repeat the subject of the question in the followup question. For example, if the question is "What is the capital of France?", the followup question should not be "What is the population of France?". Instead, it should be "How many people live there?" or "What is its population?". # Context: {context} From 49da0961e5d1bfcd3e65dff6b01254de843ae411 Mon Sep 17 00:00:00 2001 From: bkb2135 <98138173+bkb2135@users.noreply.github.com> Date: Thu, 13 Jun 2024 19:35:42 -0400 Subject: [PATCH 13/16] SN1-12 --- prompting/tasks/qa.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prompting/tasks/qa.py b/prompting/tasks/qa.py index 133754ddd..d8a6e91a8 100644 --- a/prompting/tasks/qa.py +++ b/prompting/tasks/qa.py @@ -47,7 +47,7 @@ """ # TODO: We also need a special followup reference prompt (or just merge both) -# TODO: We should +# TODO: We should create followups using the specified llama3 chat template rather than feeding the message history through textually FOLLOWUP_REFERENCE_PROMPT_TEMPLATE = """\ You are a helpful assistant. Answer the question below in detail, prioritizing the use of the provided conversation history. The context is available for additional information if needed, but it may not always be relevant. From 523e00a957632bdfed35e92b3cb00d74e35d891b Mon Sep 17 00:00:00 2001 From: p-ferreira Date: Fri, 14 Jun 2024 19:16:17 +0000 Subject: [PATCH 14/16] drops block from log data --- prompting/base/prompting_miner.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/prompting/base/prompting_miner.py b/prompting/base/prompting_miner.py index 3630ec75b..1668f96ff 100644 --- a/prompting/base/prompting_miner.py +++ b/prompting/base/prompting_miner.py @@ -172,7 +172,8 @@ def log_event( dendrite_uid = self.metagraph.hotkeys.index(synapse.dendrite.hotkey) step_log = { "epoch_time": timing, - "block": self.last_epoch_block, + # TODO: add block to logs in the future in a way that doesn't impact performance + # "block": self.block, "messages": messages, "accumulated_chunks": accumulated_chunks, "accumulated_chunks_timings": accumulated_chunks_timings, From 629e014149f3ab2ef67bfce0c93799114ce6d209 Mon Sep 17 00:00:00 2001 From: bkb2135 Date: Mon, 17 Jun 2024 17:50:50 +0000 Subject: [PATCH 15/16] Switch last_update_block to local var instead of metagraph attribute --- prompting/base/miner.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/prompting/base/miner.py b/prompting/base/miner.py index e23cbcbf4..f0653abef 100644 --- a/prompting/base/miner.py +++ b/prompting/base/miner.py @@ -104,12 +104,12 @@ def run(self): self.axon.start() bt.logging.info(f"Miner starting at block: {self.block}") - + last_update_block = 0 # This loop maintains the miner's operations until intentionally stopped. try: while not self.should_exit: while ( - self.block - self.metagraph.last_update[self.uid] + self.block - last_update_block < self.config.neuron.epoch_length ): # Wait before checking again. @@ -121,6 +121,7 @@ def run(self): # Sync metagraph and potentially set weights. self.sync() + last_update_block = self.block self.step += 1 # If someone intentionally stops the miner, it'll safely terminate operations. From 711408056626481d8d0104a25969df597a6cf4a5 Mon Sep 17 00:00:00 2001 From: bkb2135 <98138173+bkb2135@users.noreply.github.com> Date: Tue, 18 Jun 2024 09:29:58 -0400 Subject: [PATCH 16/16] Update README.md --- README.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index f78fc33a1..249721626 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,10 @@ git clone https://github.com/opentensor/prompting.git cd prompting bash install.sh ``` +If you are running a miner, you will also need to uninstall uvloop. +```bash +pip uninstall uvloop -y +``` @@ -70,9 +74,8 @@ python ``` where `SCRIPT_PATH` is either: -1. neurons/miners/huggingface/miner.py -2. neurons/miners/openai/miner.py -3. neurons/validator.py +1. neurons/miners/openai/miner.py +2. neurons/validator.py For ease of use, you can run the scripts as well with PM2. Installation of PM2 is: **On Linux**: @@ -80,10 +83,10 @@ For ease of use, you can run the scripts as well with PM2. Installation of PM2 i sudo apt update && sudo apt install jq && sudo apt install npm && sudo npm install pm2 -g && pm2 update ``` -Example of running a Llama3 miner: +Example of running an Openai miner on Main: ```bash -pm2 start neurons/miners/huggingface/miner.py --interpreter python3 --name llama3_miner -- --netuid 1 --subtensor.network finney --wallet.name my_wallet --wallet.hotkey m1 --neuron.model_id casperhansen/llama-3-70b-instruct-awq --neuron.load_in_4bit True --axon.port 21988 --logging.debug +pm2 start neurons/miners/openai/miner.py --interpreter python --name openai_miner -- --netuid 1 --subtensor.network finney --wallet.name my_wallet --wallet.hotkey my_hotkey --neuron.model_id gpt-3.5-turbo-1106 --axon.port 8091 ``` # Testnet @@ -94,7 +97,7 @@ In order to run on testnet, you will need to go through the same hotkey registra To run: ```bash -pm2 start neurons/miners/huggingface/miner.py --interpreter python3 --name llama3_miner -- --netuid 61 --subtensor.network test --wallet.name my_test_wallet --wallet.hotkey m1 --neuron.model_id casperhansen/llama-3-70b-instruct-awq --neuron.load_in_4bit True --axon.port 21988 --logging.debug +pm2 start neurons/miners/openai/miner.py --interpreter python3 --name openai_miner -- --netuid 61 --subtensor.network test --wallet.name my_test_wallet --wallet.hotkey my_test_hotkey --neuron.model_id gpt-3.5-turbo-1106 --axon.port 8091 ``` # Limitations