Skip to content

Commit 8ad5953

Browse files
authored
Merge pull request #269 from macrocosm-os/staging
v2.5.0
2 parents 082c52a + cc981f0 commit 8ad5953

File tree

14 files changed

+301
-77
lines changed

14 files changed

+301
-77
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,9 @@ cython_debug/
163163
#.idea/
164164

165165
testing/
166+
data/*
167+
plots/*
168+
notebooks/*
166169
core
167170
app.config.js
168171
wandb

README.md

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ git clone https://github.com/opentensor/prompting.git
4242
cd prompting
4343
bash install.sh
4444
```
45+
If you are running a miner, you will also need to uninstall uvloop.
46+
```bash
47+
pip uninstall uvloop -y
48+
```
4549

4650
</div>
4751

@@ -70,22 +74,30 @@ python <SCRIPT_PATH>
7074
```
7175

7276
where `SCRIPT_PATH` is either:
73-
1. neurons/miners/huggingface/miner.py
74-
2. neurons/miners/openai/miner.py
75-
3. neurons/validator.py
77+
1. neurons/miners/openai/miner.py
78+
2. neurons/validator.py
7679

7780
For ease of use, you can run the scripts as well with PM2. Installation of PM2 is:
7881
**On Linux**:
7982
```bash
8083
sudo apt update && sudo apt install jq && sudo apt install npm && sudo npm install pm2 -g && pm2 update
8184
```
8285

83-
Example of running a Llama3 miner:
86+
Example of running an Openai miner on Main:
8487

8588
```bash
86-
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
89+
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
8790
```
8891

92+
## Running with autoupdate
93+
94+
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**.
95+
```bash
96+
pm2 start run.sh --name s1_validator_autoupdate -- --wallet.name <your-wallet-name> --wallet.hotkey <your-wallet-hot-key>
97+
```
98+
99+
> 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.
100+
89101
# Testnet
90102
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**.
91103

@@ -94,7 +106,7 @@ In order to run on testnet, you will need to go through the same hotkey registra
94106
To run:
95107

96108
```bash
97-
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
109+
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
98110
```
99111

100112
# Limitations

neurons/miners/huggingface/miner.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,11 @@
1717
import time
1818
import bittensor as bt
1919
from prompting.miners import HuggingFaceMiner
20+
from deprecated import deprecated
2021

2122

22-
# This is the main function, which runs the miner.
23-
if __name__ == "__main__":
23+
@deprecated(version="2.4.1+", reason="Class is deprecated, use openai miner for reference on example miner.")
24+
def main():
2425
with HuggingFaceMiner() as miner:
2526
while True:
2627
miner.log_status()
@@ -29,3 +30,7 @@
2930
if miner.should_exit:
3031
bt.logging.warning("Ending miner...")
3132
break
33+
34+
35+
if __name__ == "__main__":
36+
main()

prompting/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
# DEALINGS IN THE SOFTWARE.
1717

1818
# Define the version of the template module.
19-
__version__ = "2.4.2"
19+
__version__ = "2.5.0"
2020
version_split = __version__.split(".")
2121
__spec_version__ = (
2222
(10000 * int(version_split[0]))

prompting/base/miner.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,12 +104,12 @@ def run(self):
104104
self.axon.start()
105105

106106
bt.logging.info(f"Miner starting at block: {self.block}")
107-
107+
last_update_block = 0
108108
# This loop maintains the miner's operations until intentionally stopped.
109109
try:
110110
while not self.should_exit:
111111
while (
112-
self.block - self.metagraph.last_update[self.uid]
112+
self.block - last_update_block
113113
< self.config.neuron.epoch_length
114114
):
115115
# Wait before checking again.
@@ -121,6 +121,7 @@ def run(self):
121121

122122
# Sync metagraph and potentially set weights.
123123
self.sync()
124+
last_update_block = self.block
124125
self.step += 1
125126

126127
# If someone intentionally stops the miner, it'll safely terminate operations.

prompting/base/prompting_miner.py

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from prompting.protocol import StreamPromptingSynapse
2424
from prompting.base.miner import BaseStreamMinerNeuron
2525
from datetime import datetime
26-
26+
from typing import List, Dict
2727

2828
class BaseStreamPromptingMiner(BaseStreamMinerNeuron):
2929
"""
@@ -159,27 +159,38 @@ def init_wandb(self):
159159

160160
def log_event(
161161
self,
162+
synapse: StreamPromptingSynapse,
162163
timing: float,
163-
prompt: str,
164-
completion: str,
165-
system_prompt: str,
164+
messages,
165+
accumulated_chunks: List[str] = [],
166+
accumulated_chunks_timings: List[float] = [],
166167
extra_info: dict = {},
167168
):
168169
if not getattr(self, "wandb_run", None):
169170
self.init_wandb()
170-
171+
172+
dendrite_uid = self.metagraph.hotkeys.index(synapse.dendrite.hotkey)
171173
step_log = {
172174
"epoch_time": timing,
173-
# "block": self.last_epoch_block,
174-
"prompt": prompt,
175-
"completion": completion,
176-
"system_prompt": system_prompt,
177-
"uid": self.metagraph.hotkeys.index(self.wallet.hotkey.ss58_address),
178-
"stake": self.metagraph.S[self.uid].item(),
179-
"trust": self.metagraph.T[self.uid].item(),
180-
"incentive": self.metagraph.I[self.uid].item(),
181-
"consensus": self.metagraph.C[self.uid].item(),
182-
"dividends": self.metagraph.D[self.uid].item(),
175+
# TODO: add block to logs in the future in a way that doesn't impact performance
176+
# "block": self.block,
177+
"messages": messages,
178+
"accumulated_chunks": accumulated_chunks,
179+
"accumulated_chunks_timings": accumulated_chunks_timings,
180+
"validator_uid": dendrite_uid,
181+
"validator_ip": synapse.dendrite.ip,
182+
"validator_coldkey": self.metagraph.coldkeys[dendrite_uid],
183+
"validator_hotkey": self.metagraph.hotkeys[dendrite_uid],
184+
"validator_stake": self.metagraph.S[dendrite_uid].item(),
185+
"validator_trust": self.metagraph.T[dendrite_uid].item(),
186+
"validator_incentive": self.metagraph.I[dendrite_uid].item(),
187+
"validator_consensus": self.metagraph.C[dendrite_uid].item(),
188+
"validator_dividends": self.metagraph.D[dendrite_uid].item(),
189+
"miner_stake": self.metagraph.S[self.uid].item(),
190+
"miner_trust": self.metagraph.T[self.uid].item(),
191+
"miner_incentive": self.metagraph.I[self.uid].item(),
192+
"miner_consensus": self.metagraph.C[self.uid].item(),
193+
"miner_dividends": self.metagraph.D[self.uid].item(),
183194
**extra_info,
184195
}
185196

prompting/forward.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
from prompting.utils.uids import get_random_uids
3737
from dataclasses import dataclass
3838

39+
SINGLE_TURN_TASKS = ['sentiment', 'translation']
40+
3941
@async_log
4042
async def generate_reference(agent):
4143
loop = asyncio.get_running_loop()
@@ -321,6 +323,9 @@ async def forward(self):
321323
if random.random()<0.5 or turn>=1:
322324
break
323325

326+
if task.name in SINGLE_TURN_TASKS:
327+
break
328+
324329
history = '\n'.join([f"{role}: {message}" for role, message in zip(roles, messages)])
325330

326331
# Use PREVIOUS task context

prompting/miners/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,4 @@
44
from .phrase import PhraseMiner
55

66
# Real miners
7-
from .hf_miner import HuggingFaceMiner
8-
from .openai_miner import OpenAIMiner
7+
from .openai_miner import OpenAIMiner

prompting/miners/hf_miner.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,9 @@
2828

2929
# import base miner class which takes care of most of the boilerplate
3030
from prompting.base.prompting_miner import BaseStreamPromptingMiner
31+
from deprecated import deprecated
3132

32-
33+
@deprecated(version="2.4.1+", reason="Class is deprecated, use openai miner for reference on example miner.")
3334
class HuggingFaceMiner(BaseStreamPromptingMiner):
3435
"""
3536
Base miner which runs zephyr (https://huggingface.co/HuggingFaceH4/zephyr-7b-beta)
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
# The MIT License (MIT)
2+
# Copyright © 2024 Yuma Rao
3+
4+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
5+
# documentation files (the “Software”), to deal in the Software without restriction, including without limitation
6+
# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
7+
# and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8+
9+
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of
10+
# the Software.
11+
12+
# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
13+
# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
14+
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
15+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
16+
# DEALINGS IN THE SOFTWARE.
17+
18+
import time
19+
import os
20+
import bittensor as bt
21+
import argparse
22+
from starlette.types import Send
23+
from functools import partial
24+
from typing import Dict, Awaitable
25+
26+
# Bittensor Miner Template:
27+
from prompting.base.prompting_miner import BaseStreamPromptingMiner
28+
from prompting.protocol import StreamPromptingSynapse
29+
30+
# import base miner class which takes care of most of the boilerplate
31+
32+
from prompting.miners.utils import OpenAIUtils
33+
34+
from langchain.prompts import ChatPromptTemplate
35+
from langchain_core.output_parsers import StrOutputParser
36+
from langchain.chat_models import ChatOpenAI
37+
from dotenv import load_dotenv, find_dotenv
38+
from langchain_core.runnables.base import RunnableSequence
39+
from deprecated import deprecated
40+
41+
@deprecated(version="2.4.1+", reason="Class is deprecated, use openai miner for reference on example miner.")
42+
class LangchainMiner(BaseStreamPromptingMiner, OpenAIUtils):
43+
"""Langchain-based miner which uses OpenAI's API as the LLM.
44+
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.
45+
You should also install the dependencies for this miner, which can be found in the requirements.txt file in this directory.
46+
"""
47+
48+
@classmethod
49+
def add_args(cls, parser: argparse.ArgumentParser):
50+
"""
51+
Adds OpenAI-specific arguments to the command line parser.
52+
"""
53+
super().add_args(parser)
54+
55+
def __init__(self, config=None):
56+
super().__init__(config=config)
57+
58+
bt.logging.info(f"Initializing with model {self.config.neuron.model_id}...")
59+
60+
if self.config.wandb.on:
61+
self.identity_tags = ("openai_miner",) + (self.config.neuron.model_id,)
62+
63+
_ = load_dotenv(find_dotenv())
64+
api_key = os.environ.get("OPENAI_API_KEY")
65+
66+
# Set openai key and other args
67+
self.model = ChatOpenAI(
68+
api_key=api_key,
69+
model_name=self.config.neuron.model_id,
70+
max_tokens=self.config.neuron.max_tokens,
71+
temperature=self.config.neuron.temperature,
72+
)
73+
74+
self.system_prompt = self.config.neuron.system_prompt
75+
self.accumulated_total_tokens = 0
76+
self.accumulated_prompt_tokens = 0
77+
self.accumulated_completion_tokens = 0
78+
self.accumulated_total_cost = 0
79+
80+
def forward(self, synapse: StreamPromptingSynapse) -> Awaitable:
81+
async def _forward(
82+
self,
83+
message: str,
84+
init_time: float,
85+
timeout_threshold: float,
86+
chain: RunnableSequence,
87+
chain_formatter: Dict[str, str],
88+
send: Send,
89+
):
90+
buffer = []
91+
temp_completion = "" # for wandb logging
92+
timeout_reached = False
93+
94+
try:
95+
# Langchain built in streaming. 'astream' also available for async
96+
for token in chain.stream(chain_formatter):
97+
buffer.append(token)
98+
99+
if time.time() - init_time > timeout_threshold:
100+
bt.logging.debug(f"⏰ Timeout reached, stopping streaming")
101+
timeout_reached = True
102+
break
103+
104+
if len(buffer) == self.config.neuron.streaming_batch_size:
105+
joined_buffer = "".join(buffer)
106+
temp_completion += joined_buffer
107+
bt.logging.debug(f"Streamed tokens: {joined_buffer}")
108+
109+
await send(
110+
{
111+
"type": "http.response.body",
112+
"body": joined_buffer.encode("utf-8"),
113+
"more_body": True,
114+
}
115+
)
116+
buffer = []
117+
118+
if (
119+
buffer and not timeout_reached
120+
): # Don't send the last buffer of data if timeout.
121+
joined_buffer = "".join(buffer)
122+
await send(
123+
{
124+
"type": "http.response.body",
125+
"body": joined_buffer.encode("utf-8"),
126+
"more_body": False,
127+
}
128+
)
129+
130+
except Exception as e:
131+
bt.logging.error(f"Error in forward: {e}")
132+
if self.config.neuron.stop_on_forward_exception:
133+
self.should_exit = True
134+
135+
finally:
136+
synapse_latency = time.time() - init_time
137+
if self.config.wandb.on:
138+
self.log_event(
139+
timing=synapse_latency,
140+
prompt=message,
141+
completion=temp_completion,
142+
system_prompt=self.system_prompt,
143+
)
144+
145+
bt.logging.debug(f"📧 Message received, forwarding synapse: {synapse}")
146+
147+
prompt = ChatPromptTemplate.from_messages(
148+
[("system", self.system_prompt), ("user", "{input}")]
149+
)
150+
chain = prompt | self.model | StrOutputParser()
151+
152+
role = synapse.roles[-1]
153+
message = synapse.messages[-1]
154+
155+
chain_formatter = {"role": role, "input": message}
156+
157+
init_time = time.time()
158+
timeout_threshold = synapse.timeout
159+
160+
token_streamer = partial(
161+
_forward,
162+
self,
163+
message,
164+
init_time,
165+
timeout_threshold,
166+
chain,
167+
chain_formatter,
168+
)
169+
return synapse.create_streaming_response(token_streamer)

0 commit comments

Comments
 (0)