Molten-Ice commited on
Commit
5a00cfd
·
0 Parent(s):

Duplicate from sortxyz/RedPajama-Inference-dev

Browse files
Files changed (6) hide show
  1. .gitattributes +1 -0
  2. README.md +13 -0
  3. app.py +312 -0
  4. callbacks.py +61 -0
  5. config.json +35 -0
  6. requirements.txt +10 -0
.gitattributes ADDED
@@ -0,0 +1 @@
 
 
1
+ *.bin filter=lfs diff=lfs merge=lfs -text
README.md ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: RedPajama-Inference-development
3
+ description: Fine-tuned RedPajama-INCITE-Chat-3B-v1 model
4
+ emoji: 👨‍💻
5
+ colorFrom: red
6
+ colorTo: gray
7
+ sdk: gradio
8
+ sdk_version: 3.34.0
9
+ app_file: app.py
10
+ pinned: false
11
+ license: apache-2.0
12
+ duplicated_from: sortxyz/RedPajama-Inference-dev
13
+ ---
app.py ADDED
@@ -0,0 +1,312 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ from pprint import pprint
3
+ from typing import List, Dict, Optional, Generator
4
+ # Loading and printing out config file (want it to be at top of hf space logs)
5
+ def load_config(file_path: str = 'config.json') -> Dict:
6
+ with open(file_path, 'r') as file:
7
+ config = json.load(file)
8
+ pprint(config)
9
+ return config
10
+ config = load_config('config.json')
11
+
12
+ ### Importing Dependencies, automatically installed from requirements.txt on huggingface space startup) ###
13
+ import os
14
+ import re
15
+ import shutil
16
+ import functools
17
+ from datetime import datetime
18
+
19
+ import torch
20
+ import numpy as np
21
+ import gradio as gr
22
+ import huggingface_hub
23
+ from datasets import Dataset
24
+ from transformers import AutoTokenizer, AutoModelForCausalLM, GenerationConfig, StoppingCriteria, StoppingCriteriaList
25
+ from peft import LoraConfig, get_peft_model, PeftModel, PeftConfig, get_peft_model_state_dict
26
+ from callbacks import Iteratorize
27
+
28
+ base_dir = os.path.dirname(os.path.abspath('__file__'))
29
+ print(f"Base directory: {base_dir}")
30
+
31
+ # Looking in "Repository secrets" to get huggingface api key
32
+ HF_TOKEN = os.environ.get("HF_TOKEN")
33
+ huggingface_hub.login(HF_TOKEN)
34
+ api = huggingface_hub.HfApi()
35
+
36
+ # matching base model to specified LoRA adapater, by using last 2 letters from hf_load_model_name to determine if 3b or 7b model if needed
37
+ def extract_base_model(config: Dict) -> str:
38
+ model_variant = config['hf_load_model_name'][-2:].lower()
39
+ if model_variant == "3b":
40
+ return "togethercomputer/RedPajama-INCITE-Chat-3B-v1"
41
+ else:
42
+ return "togethercomputer/RedPajama-INCITE-7B-Chat"
43
+ base_model = extract_base_model(config)
44
+ print(f"Using base model: {base_model}")
45
+
46
+ # Creating huggingface tokenizer
47
+ tokenizer = AutoTokenizer.from_pretrained(base_model)
48
+
49
+
50
+ ### Downloading LoRA adapter & additional instruction prompt config ###
51
+ additional_instruction = "The details below description the character, utilizing any relevant detail in your response.\n" # overridden by fine-tuned config
52
+ if config['hf_load_model_name']:
53
+ if not os.path.exists("lora_weights"):
54
+ os.mkdir("lora_weights")
55
+ #Download finetuned model
56
+ huggingface_hub.hf_hub_download(repo_id=f"sortxyz/{config['hf_load_model_repo']}",
57
+ filename=f"{config['hf_load_model_name']}/adapter_model.bin",
58
+ repo_type="model",
59
+ local_dir="lora_weights")
60
+ #Download finetuned config
61
+ huggingface_hub.hf_hub_download(repo_id=f"sortxyz/{config['hf_load_model_repo']}",
62
+ filename=f"{config['hf_load_model_name']}/adapter_config.json",
63
+ repo_type="model",
64
+ local_dir="lora_weights")
65
+ huggingface_hub.hf_hub_download(repo_id=f"sortxyz/{config['hf_load_model_repo']}",
66
+ filename=f"{config['hf_load_model_name']}/additional_instruction.json",
67
+ repo_type="model",
68
+ local_dir="lora_weights")
69
+ # Using identicial prompt in inference to that which was used in training
70
+ with open(f"{base_dir}/lora_weights/{config['hf_load_model_name']}/additional_instruction.json", 'r') as file:
71
+ additional_instruction = json.load(file)['additional_instruction']
72
+ os.remove(f"{base_dir}/lora_weights/{config['hf_load_model_name']}/additional_instruction.json")
73
+
74
+ ### Prompt formatting ###
75
+ instruction_empty_prompt = """<human>:{}\n""" + additional_instruction + """{}\n<bot>:"""
76
+ response_empty_prompt = instruction_empty_prompt + """{}\n<human>:"""
77
+ print(f"{'#'*25} Empty response prompt {'#'*25}\n" + response_empty_prompt + f"\n{'#'*23} Empty response prompt END {'#'*23}")
78
+
79
+ def reformat_row(row):
80
+ """
81
+ Converts 'attributes' list of a given row into a dictionary, reducing the string length by approximately 2x.
82
+
83
+ Parameters:
84
+ row (dict): Contains 'id' (int) and 'attributes' (list of dictionaries).
85
+ {'id': 979, 'attributes': [{'value': 'Chillbucks Apron', 'trait_type': 'Accessories'}, {'value': 'Black Shirt', 'trait_type': 'Apparel'}, {'value': 'Blue', 'trait_type': 'Background'}, {'value': 'Uhhh', 'trait_type': 'Expression'}, {'value': 'Short Curly Black', 'trait_type': 'Hair'}, {'value': 'Purple', 'trait_type': 'Skin'}]}
86
+
87
+ Returns:
88
+ dict: Original 'id' with 'trait_type' and 'value' pairs from 'attributes' as new keys and values.
89
+ {'id': 979, 'Accessories': 'Chillbucks Apron', 'Apparel': 'Black Shirt', 'Background': 'Blue', 'Expression': 'Uhhh', 'Hair': 'Short Curly Black', 'Skin': 'Purple'}
90
+ """
91
+ return {**{'id': row['id']}, **{pair['trait_type'] : pair['value'] for pair in row['attributes']}}
92
+
93
+ ### Downloading full dataset for character lookup & test dataset for loss metric evaluation ###
94
+ if not os.path.exists("downloaded_data"):
95
+ os.mkdir("downloaded_data")
96
+ huggingface_hub.hf_hub_download(repo_id=f"sortxyz/{config['hf_complete_dataset_repo']}",
97
+ filename=config['hf_complete_dataset_name'],
98
+ repo_type="dataset",
99
+ local_dir="downloaded_data")
100
+ shutil.move(f"downloaded_data/{config['hf_complete_dataset_name']}", 'complete_dataset.jsonl')
101
+
102
+ ### Load complete_dataset, format it, and create dict for characters to be referenced by ID ###
103
+ # Currently using 991 rows, hopefully should directly scale up to 20,000 without further finetuning
104
+ with open('complete_dataset.jsonl', 'r') as fp:
105
+ complete_dataset_raw = [json.loads(x) for x in fp.readlines()]
106
+ complete_dataset = [reformat_row(data_dict) for data_dict in complete_dataset_raw]
107
+ data_indexed_by_id = {data_dict['id'] : data_dict for data_dict in complete_dataset} # Used for inference
108
+ print(f"length of complete_dataset: {len(complete_dataset)}")
109
+
110
+ def find_relevant_rows(instruction):
111
+ """Used to append relevant information to character(s) referenced in prompt
112
+ Given a string it extracts the corresponding rows
113
+ e.g. "Describe ID 972 and 979" returns
114
+ {'id': 972, 'Accessories': 'Smoke Frame Glasses', 'Apparel': 'Blue Button Up', 'Background': 'Blue', 'Expression': 'Chill Smile', 'Facial Features': 'Stuble Goatee', 'Hair': 'Short Blond', 'Skin': 'Orange'}
115
+ {'id': 979, 'Accessories': 'Chillbucks Apron', 'Apparel': 'Black Shirt', 'Background': 'Blue', 'Expression': 'Uhhh', 'Hair': 'Short Curly Black', 'Skin': 'Purple'}
116
+ """
117
+ ids_found = [int(n) for n in re.findall(r'\d+', instruction)]
118
+ rows_returned = []
119
+ for id in ids_found:
120
+ if id not in data_indexed_by_id.keys():
121
+ rows_returned.append(f"ID {id} is not contained in dataset")
122
+ else:
123
+ rows_returned.append(str(data_indexed_by_id[id]))
124
+ return "\n".join(rows_returned)
125
+
126
+ ### Load persistent dataset repo ###
127
+ persistent_dataset_repo_url = "https://huggingface.co/datasets/sortxyz/persistent-space-dataset"
128
+ persistent_data_filename = f"{base_dir}/data/{config['persistent_data_filename']}"
129
+
130
+ repo = huggingface_hub.Repository(local_dir="data", clone_from=persistent_dataset_repo_url, use_auth_token=HF_TOKEN)
131
+ if not os.path.exists(persistent_data_filename):
132
+ persistent_data = {"data": []} # user_data.json has not been created
133
+ else:
134
+ with open(persistent_data_filename, 'r') as f:
135
+ persistent_data = json.load(f) # load existing data, which will then be appended to
136
+
137
+ def store_persistent_information(user_prompt: str, model_prompt: str, generate_output: str, print_commit_url=False):
138
+ """Appends message to persistent data .json file and uploads it to hf dataset repo on every call.
139
+ Note: repo is only retrieved at the start of the runtime, so will not work for spaces in parallele"""
140
+ persistent_data['data'].append({"user_prompt": user_prompt, "model_prompt": model_prompt, "generate_output": generate_output, "time": datetime.now().strftime('%Y-%m-%d %H:%M:%S')})
141
+ with open(persistent_data_filename, 'w') as f:
142
+ json.dump(persistent_data, f, indent=4)
143
+ commit_url = repo.push_to_hub()
144
+ if print_commit_url:
145
+ print(commit_url)
146
+
147
+ class StopWordsCriteria(StoppingCriteria):
148
+ """
149
+ Class for stopping output generation when the "<human>" token is encountered.
150
+ Prevents the display of any remaining unwanted tokens during streaming.
151
+ Buffering would be faster, but it is incompatible with multiple beams,
152
+ so we must decode after every new token.
153
+ """
154
+
155
+ def __init__(self, tokenizer, stop_words: list = ["<human>"], prompt_length: int = -1, stream_callback: bool = False):
156
+ self._tokenizer = tokenizer
157
+ self._stop_words = stop_words
158
+ self.prompt_length = prompt_length
159
+ self._stream_callback = stream_callback
160
+
161
+ def __call__(self, input_ids: torch.LongTensor, scores: torch.FloatTensor, **kwargs) -> bool:
162
+ if self.prompt_length == -1:
163
+ self.prompt_length = len(input_ids[0]) - 1
164
+
165
+ # Decode model output into text, excluding the prompt
166
+ decoded_text = self._tokenizer.decode(input_ids[0][self.prompt_length:])
167
+ for stop_word in self._stop_words:
168
+ if stop_word in decoded_text:
169
+ return True
170
+
171
+ if self._stream_callback:
172
+ # Skips last characters if they are part of "<human>"
173
+ last_characters_to_miss = 0
174
+ for stop_word in self._stop_words:
175
+ for i in range(1, len(stop_word)):
176
+ if decoded_text.endswith(stop_word[0:i]):
177
+ last_characters_to_miss = max(i, last_characters_to_miss)
178
+
179
+ # Sends data to Gradio using callback, allows interruption of huggingface .generate function after each token
180
+ self._stream_callback(decoded_text) if last_characters_to_miss == 0 else self._stream_callback(decoded_text[:-last_characters_to_miss])
181
+ return False
182
+
183
+ def evaluate(
184
+ model,
185
+ instruction,
186
+ temperature=0.1,
187
+ top_p=0.75,
188
+ top_k=40,
189
+ num_beams=4,
190
+ max_new_tokens=256,
191
+ stream_output=False,
192
+ store_outputs=True,
193
+ print_progress=True,
194
+ **kwargs,
195
+ ):
196
+ """Evaluate the model using the provided instruction and generation parameters.
197
+ Yields:
198
+ The generated output as a response to the provided instruction (Directly or via streaming).
199
+ """
200
+ prompt = instruction_empty_prompt.format(instruction, find_relevant_rows(instruction))
201
+ if print_progress:
202
+ print("\n" + "#"*50 + "\n" + prompt)
203
+
204
+ # tokenizer input string
205
+ inputs = tokenizer(prompt, return_tensors="pt")
206
+ input_ids = inputs["input_ids"].to("cuda:0") # torch.device(
207
+
208
+ #configuration setting for model generation
209
+ generate_params = {
210
+ "input_ids": input_ids,
211
+ "generation_config": GenerationConfig(
212
+ temperature=temperature,
213
+ top_p=top_p,
214
+ top_k=top_k,
215
+ num_beams=num_beams,
216
+ **kwargs),
217
+ "return_dict_in_generate": True,
218
+ "output_scores": True,
219
+ "max_new_tokens": max_new_tokens,
220
+ }
221
+
222
+ if stream_output:
223
+ ### Generate with streaming ###
224
+ # Streaming the reply 1 token at a time, based on the trick of using 'stopping_criteria' to create an iterator, which is then tracked with a callback
225
+ # ref - https://github.com/oobabooga/text-generation-webui/blob/ad37f396fc8bcbab90e11ecf17c56c97bfbd4a9c/modules/text_generation.py#L216-L243.
226
+
227
+ def generate_with_callback(callback=None, **kwargs):
228
+ kwargs.setdefault(
229
+ "stopping_criteria", StoppingCriteriaList(
230
+ [StopWordsCriteria(tokenizer,
231
+ stream_callback=callback)])
232
+ )
233
+ with torch.no_grad():
234
+ model.generate(**kwargs)
235
+
236
+ def generate_with_streaming(**kwargs):
237
+ return Iteratorize(generate_with_callback, kwargs, callback=None)
238
+
239
+ with generate_with_streaming(**generate_params) as generator:
240
+ for output in generator:
241
+ yield output
242
+ else:
243
+ ### Generate without streaming ###
244
+ with torch.no_grad():
245
+ generation_output = model.generate(
246
+ **generate_params,
247
+ stopping_criteria=StoppingCriteriaList([StopWordsCriteria(tokenizer)])
248
+ )
249
+ unformatted_output = tokenizer.decode(generation_output.sequences[0])
250
+ # Truncate the input prompt, remove unwanted final tokens, strip whitespace and newlines from ends)
251
+ output = unformatted_output[len(prompt):-len('<human>:<|endoftext|>')].strip(' \n')
252
+ if print_progress:
253
+ print(output)
254
+ if store_outputs:
255
+ store_persistent_information(instruction, prompt, output, print_commit_url=True)
256
+ yield output
257
+
258
+ def create_gradio_interface(
259
+ model: PeftModel,
260
+ config: Dict,
261
+ ) -> gr.Interface:
262
+ """Creates and launches a Gradio Interface using given model and configuration parameters.
263
+
264
+ Args:
265
+ model (PeftModel): The model to be evaluated.
266
+ config (dict): Configuration parameters for the Gradio Interface.
267
+
268
+ Returns:
269
+ gr.Interface: Gradio interface object."""
270
+
271
+ gradio_inputs = [
272
+ gr.components.Textbox(lines=2, label="Instruction", placeholder=config['gradio_placeholder'], value=config['gradio_value']),
273
+ gr.components.Slider(minimum=0, maximum=1, value=0.1, label="Temperature"),
274
+ gr.components.Slider(minimum=0, maximum=1, value=0.75, label="Top p"),
275
+ gr.components.Slider(minimum=0, maximum=100, step=1, value=40, label="Top k"),
276
+ gr.components.Slider(minimum=1, maximum=4, step=1, value=4, label="Beams"),
277
+ gr.components.Slider(minimum=1, maximum=2000, step=1, value=256, label="Max tokens"),
278
+ gr.components.Checkbox(label="Stream output", value=True),
279
+ gr.components.Checkbox(label="Store user prompts (to improve future model versions)", value=True),
280
+ ]
281
+
282
+ interface = gr.Interface(
283
+ fn=functools.partial(evaluate, model),
284
+ inputs=gradio_inputs,
285
+ outputs=[gr.inputs.Textbox(lines=5, label="Generation")],
286
+ title=config['gradio_title'],
287
+ description=config['gradio_description'],
288
+ examples=config['gradio_examples'],
289
+ cache_examples=False
290
+ )
291
+
292
+ interface.queue(concurrency_count=config['concurrency_count'])
293
+ return interface
294
+
295
+ ### Load base model ###
296
+ model = AutoModelForCausalLM.from_pretrained(
297
+ base_model,
298
+ load_in_8bit=True,
299
+ device_map="auto")
300
+
301
+ ### Loading LoRA adapter (if specified) ###
302
+ if config['hf_load_model_name']:
303
+ model = PeftModel.from_pretrained(
304
+ model,
305
+ f"lora_weights/{config['hf_load_model_name']}")
306
+
307
+ # Loss dataset values (each model trained for 3 epochs on 10k prompt dataset)
308
+ # 7B Model: 2.40430->1.11667
309
+ # 3B Model: 3.95312->1.07058
310
+
311
+ ### indefinitely run gradio interface ###
312
+ create_gradio_interface(model, config).launch(debug=True)
callbacks.py ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Helpers to support streaming generate output.
3
+ Borrowed from https://github.com/oobabooga/text-generation-webui/blob/ad37f396fc8bcbab90e11ecf17c56c97bfbd4a9c/modules/callbacks.py
4
+ """
5
+
6
+ import gc
7
+ import traceback
8
+ from queue import Queue
9
+ from threading import Thread
10
+
11
+ class Iteratorize:
12
+
13
+ """
14
+ Transforms a function that takes a callback
15
+ into a lazy iterator (generator).
16
+ """
17
+
18
+ def __init__(self, func, kwargs={}, callback=None):
19
+ self.mfunc = func
20
+ self.c_callback = callback
21
+ self.q = Queue()
22
+ self.sentinel = object()
23
+ self.kwargs = kwargs
24
+ self.stop_now = False
25
+
26
+ def _callback(val):
27
+ if self.stop_now:
28
+ raise ValueError
29
+ self.q.put(val)
30
+
31
+ def gentask():
32
+ try:
33
+ ret = self.mfunc(callback=_callback, **self.kwargs)
34
+ except ValueError:
35
+ pass
36
+ except:
37
+ traceback.print_exc()
38
+ pass
39
+
40
+ self.q.put(self.sentinel)
41
+ if self.c_callback:
42
+ self.c_callback(ret)
43
+
44
+ self.thread = Thread(target=gentask)
45
+ self.thread.start()
46
+
47
+ def __iter__(self):
48
+ return self
49
+
50
+ def __next__(self):
51
+ obj = self.q.get(True, None)
52
+ if obj is self.sentinel:
53
+ raise StopIteration
54
+ else:
55
+ return obj
56
+
57
+ def __enter__(self):
58
+ return self
59
+
60
+ def __exit__(self, exc_type, exc_val, exc_tb):
61
+ self.stop_now = True
config.json ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "gradio_title": "RedPajama Chillennials Chat Bot",
3
+ "gradio_description": "Fine-tuned RedPajama-Chat model talior to Chillennials the NFT collection. Click on the examples at the bottom for some prompt inspiration",
4
+ "persistent_data_filename": "user_data_dev.json",
5
+ "hf_load_model_name": "finetuned_10k_prompts_v1_3b",
6
+ "hf_load_model_repo": "finetuned_models",
7
+ "concurrency_count": 3,
8
+ "hf_complete_dataset_name": "complete_data.jsonl",
9
+ "hf_complete_dataset_repo": "complete_dataset",
10
+ "gradio_value": "Describe Chillennial 972 to me, only including the core info.",
11
+ "gradio_placeholder": "Type a prompt here and click \"Submit\"",
12
+ "gradio_examples": [
13
+ [
14
+ "Tell me about Chillennial 972."
15
+ ],
16
+ [
17
+ "Describe character 900, only including core details."
18
+ ],
19
+ [
20
+ "Write a dialogue between character 742 and a character of your own creation called Bryan"
21
+ ],
22
+ [
23
+ "Create a quick synopsis of a movie where character 761 is the main character."
24
+ ],
25
+ [
26
+ "Write a short poem inspired by the details of digital character 727."
27
+ ],
28
+ [
29
+ "Envision what character 731's dream vacation would look like."
30
+ ],
31
+ [
32
+ "Recommend three activities for a perfect weekend for character 745."
33
+ ]
34
+ ]
35
+ }
requirements.txt ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ datasets
2
+ loralib
3
+ sentencepiece
4
+ git+https://github.com/huggingface/transformers.git
5
+ git+https://github.com/huggingface/peft.git
6
+ gradio
7
+ bitsandbytes
8
+ transformers
9
+ accelerate
10
+ scipy