AlexKitipov commited on
Commit
5d9a998
·
verified ·
1 Parent(s): 958f0aa

Delete phi2Finetune.ipynb

Browse files
Files changed (1) hide show
  1. phi2Finetune.ipynb +0 -858
phi2Finetune.ipynb DELETED
@@ -1,858 +0,0 @@
1
- {
2
- "cells": [
3
- {
4
- "cell_type": "markdown",
5
- "metadata": {},
6
- "source": [
7
- "Github Project: dstoolkit-phi2-finetune "
8
- ]
9
- },
10
- {
11
- "cell_type": "markdown",
12
- "metadata": {
13
- "nteract": {
14
- "transient": {
15
- "deleting": false
16
- }
17
- }
18
- },
19
- "source": [
20
- "# Finetune Phi-2"
21
- ]
22
- },
23
- {
24
- "cell_type": "markdown",
25
- "metadata": {},
26
- "source": [
27
- "This finetuning setup was performed on Azure Machine Learning on a VM with a GPU (Standard_NC6s_v3 - 1 x NVIDIA Tesla V100). Once the enviroment is created using the associated requirements.txt file on the VM, its much cleaner to open this notebook on VS code if you are using AML, or any other means to open a notebook. \n",
28
- "\n",
29
- "!NOTE: Compared to GPT models, there has been little work done to make the content of Phi-2 safe, therefore beware about using this model out of the box without safety checks/ logic such as Azure Content Moderator."
30
- ]
31
- },
32
- {
33
- "cell_type": "markdown",
34
- "metadata": {},
35
- "source": [
36
- "#### Enviroment Setup"
37
- ]
38
- },
39
- {
40
- "cell_type": "markdown",
41
- "metadata": {},
42
- "source": [
43
- "Clone this repo \n",
44
- "\n",
45
- " git clone https://github.com/microsoft/dstoolkit-phi2-finetune.git\n",
46
- "\n",
47
- "Once the repo has been cloned, create a new Python enviroment and activate it\n",
48
- "\n",
49
- " python -m virtualenv env\n",
50
- " env\\Scripts\\Activate\n",
51
- "\n",
52
- "Install Python requirements from requirements.txt\n",
53
- "\n",
54
- " pip install -r requirements.txt"
55
- ]
56
- },
57
- {
58
- "cell_type": "markdown",
59
- "metadata": {},
60
- "source": [
61
- "#### Setup"
62
- ]
63
- },
64
- {
65
- "cell_type": "code",
66
- "execution_count": 1,
67
- "metadata": {
68
- "gather": {
69
- "logged": 1708528771565
70
- }
71
- },
72
- "outputs": [
73
- {
74
- "name": "stderr",
75
- "output_type": "stream",
76
- "text": [
77
- "/anaconda/envs/phi2ENV/lib/python3.8/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
78
- " from .autonotebook import tqdm as notebook_tqdm\n"
79
- ]
80
- }
81
- ],
82
- "source": [
83
- "# Import Libraries\n",
84
- "from datasets import load_dataset\n",
85
- "import torch\n",
86
- "from transformers import AutoTokenizer, AutoModelForCausalLM, Trainer, TrainingArguments, DataCollatorForLanguageModeling\n",
87
- "from peft import LoraConfig, get_peft_model"
88
- ]
89
- },
90
- {
91
- "cell_type": "markdown",
92
- "metadata": {},
93
- "source": [
94
- "#### Data Prep\n",
95
- "Prepare data into training and validation sets. Usually, in data science, we also have a test set to test our model on unseen data which replicates the real world population. In this example, we can vertify this during inference. Validation set helps us to check that our model isnt overfitting to the train set during the training process. Because our use case involves training a QnA agent, it doesnt make sense to withhold questions from the training set, therefore i will use the entire data for training, and a random subset to vertify it is learning alongside our training metrics. If your use case differs (such as using continuous text), then you may change your files and split type to suit your needs. For our training and validation we need two jsonl files which operate as line level json files.\n",
96
- "\n",
97
- "This can be formatted as key-value pairs if the text we wish to finetune is in question-answer format, or as a note if we are trying to finetune continuous text.\n",
98
- "\n",
99
- "see below for both examples (jsonl file looks like so):\n",
100
- "\n",
101
- " {\"question\": \"Does the Sun rise in the East or West?\", \"answer\": \"The Sun rises in the East.\"}\n",
102
- " {\"question\": \"What is the biggest UK festival?\", \"answer\": \"The largest UK music festival is Glastonbury.\"}\n",
103
- "OR\n",
104
- "\n",
105
- " {\"note\": \"continuousTextExample2\"}\n",
106
- " {\"note\": \"continuousTextExample2\"}\n",
107
- "\n",
108
- "We can break down sections of continuous text to make the training quicker, so each example is smaller. Whether this is done or not, there should be some form of data quality check or preprocessing step. The original paper which introduced Phi-1 (Textbooks are all you need) emphasized the need for high quality data over quantity, and the Phi models are all originally trained on relativly low quantity but high quality Python textbooks.\n",
109
- "\n",
110
- "For our example, we will use a QnA dataset and therefore the former formatting example above. I have selected the [Microsoft 365 FAQ](https://www.microsoft.com/en-us/microsoft-365/microsoft-365-for-home-and-school-faq) which contains question-answer pairs of commonly asked questions for 365 products. To take this web link and put it in the above format, i utilized [Azure Language Studio's Question-Answering](https://learn.microsoft.com/en-us/azure/ai-services/language-service/question-answering/overview) service which has the ability to parse a FAQ HTML page into question-answer pairs. These were then exported into a csv file. We can then use code or GPT (with examples) to automate the generation of the required format as a jsonl file. A code example is provided in the cell below."
111
- ]
112
- },
113
- {
114
- "cell_type": "code",
115
- "execution_count": 2,
116
- "metadata": {
117
- "gather": {
118
- "logged": 1708512707736
119
- },
120
- "jupyter": {
121
- "outputs_hidden": false,
122
- "source_hidden": false
123
- },
124
- "nteract": {
125
- "transient": {
126
- "deleting": false
127
- }
128
- }
129
- },
130
- "outputs": [],
131
- "source": [
132
- "# To convert csv/ excel to jsonl - you do not need to run this cell if you have your own data formated as above.\n",
133
- "# !NOTE: in csv, every row was another QnA pair and each column was: Question, Answer.\n",
134
- "import pandas as pd\n",
135
- "import json\n",
136
- "from numpy import random\n",
137
- "\n",
138
- "trainSplit:float=0.7 # set our split between train and validation\n",
139
- "\n",
140
- "qnaData:object=pd.read_excel(\"QnA_MSFT365.xlsx\") # read in data\n",
141
- "jsonList=list() # create empty list to store jsonl structure\n",
142
- "for index, row in qnaData.iterrows(): # iterate over rows\n",
143
- " jsonList.append({\"question\": row['Question'], \"answer\": row['Answer']}) # append in required format\n",
144
- "\n",
145
- "indexSplit:int=int(trainSplit*len(jsonList)) # get the index where the train-val split will occur\n",
146
- "random.shuffle(jsonList) # randomise list order so we can split it randomly\n",
147
- "\n",
148
- "# format into train and validation set\n",
149
- "trainSet:list=jsonList # [:indexSplit] # commented as we wish to train over entire set\n",
150
- "valSet:list=jsonList[indexSplit:]\n",
151
- "\n",
152
- "# save train and val\n",
153
- "with open(\"train.jsonl\", 'w') as f:\n",
154
- " for item in trainSet:\n",
155
- " f.write(json.dumps(item) + \"\\n\")\n",
156
- "with open(\"val.jsonl\", 'w') as f:\n",
157
- " for item in valSet:\n",
158
- " f.write(json.dumps(item) + \"\\n\")"
159
- ]
160
- },
161
- {
162
- "cell_type": "code",
163
- "execution_count": 3,
164
- "metadata": {},
165
- "outputs": [
166
- {
167
- "name": "stderr",
168
- "output_type": "stream",
169
- "text": [
170
- "Generating train split: 56 examples [00:00, 744.42 examples/s]\n",
171
- "Generating train split: 17 examples [00:00, 6966.60 examples/s]\n"
172
- ]
173
- }
174
- ],
175
- "source": [
176
- "# Load and Format Data - saved as \"train.jsonl\", \"val.jsonl\"\n",
177
- "dataName:str=\"train.jsonl\"\n",
178
- "valName:str=\"val.jsonl\"\n",
179
- "trainDataset, evalDataset = load_dataset('json', data_files=dataName, split='train'), load_dataset('json', data_files=valName, split='train')\n",
180
- "\n",
181
- "def formattingFunc(textExample:str) -> str:\n",
182
- " \"\"\"\n",
183
- " This function formats our text to be continuous rather than in json format. The output of this function is submitted directly to Phi-2 for finetuning.\n",
184
- " \"\"\"\n",
185
- " text:str=f\"Question: {textExample['question']}\\nAnswer: {textExample['answer']}\" # if QnA\n",
186
- " # text:str=f\"{example['note']}\" # if continuous text\n",
187
- " return text"
188
- ]
189
- },
190
- {
191
- "cell_type": "markdown",
192
- "metadata": {},
193
- "source": [
194
- "#### Load Model and Tokenizer\n",
195
- "This is the model which will be finetuned - will will be usig Phi-2. We will also adjust the padding in the input data so that we can determine the appropriate max_length of our input tokens. Larger max_length would be more computationally expensive so it may be worth adjusting your training, validation examples if you have large data examples. Each input will be padded with our end of sequence (eos) token."
196
- ]
197
- },
198
- {
199
- "cell_type": "code",
200
- "execution_count": 7,
201
- "metadata": {},
202
- "outputs": [
203
- {
204
- "name": "stderr",
205
- "output_type": "stream",
206
- "text": [
207
- "Loading checkpoint shards: 100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████��█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 2/2 [00:06<00:00, 3.36s/it]\n",
208
- "Special tokens have been added in the vocabulary, make sure the associated word embeddings are fine-tuned or trained.\n"
209
- ]
210
- }
211
- ],
212
- "source": [
213
- "# Load our base model\n",
214
- "baseModelName:str=\"microsoft/phi-2\"\n",
215
- "\n",
216
- "# Load our base model\n",
217
- "model:object=AutoModelForCausalLM.from_pretrained(baseModelName,\n",
218
- " torch_dtype=torch.float32, # fixes issue in inference related to float16 values producing \"!!!!\" rather than output.\n",
219
- " device_map=\"auto\",\n",
220
- " trust_remote_code=True,\n",
221
- " load_in_8bit=True)\n",
222
- "\n",
223
- "# Load our tokenizer\n",
224
- "tokenizer:object=AutoTokenizer.from_pretrained(\n",
225
- " baseModelName,\n",
226
- " padding_side=\"left\", # add padding so that our input sequences are all the same length. Left means that pad token is repeated until we reach our input text.\n",
227
- " add_eos_token=True, # end of sequence token\n",
228
- " add_bos_token=True, # beginning of sequence token\n",
229
- " use_fast=False,\n",
230
- ")\n",
231
- "tokenizer.pad_token = tokenizer.eos_token # set out pad token to be the same as eos token"
232
- ]
233
- },
234
- {
235
- "cell_type": "code",
236
- "execution_count": 8,
237
- "metadata": {},
238
- "outputs": [
239
- {
240
- "name": "stderr",
241
- "output_type": "stream",
242
- "text": [
243
- "Map: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 56/56 [00:00<00:00, 625.80 examples/s]\n",
244
- "Map: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 17/17 [00:00<00:00, 511.50 examples/s]\n",
245
- "Map: 100%|██████████████████████████████████████████████████████████████████████████████████████��██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 56/56 [00:00<00:00, 627.35 examples/s]\n",
246
- "Map: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████���███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 17/17 [00:00<00:00, 482.16 examples/s]"
247
- ]
248
- },
249
- {
250
- "name": "stdout",
251
- "output_type": "stream",
252
- "text": [
253
- "| Diff Token Size |\n",
254
- "Original Lengths: 261\n",
255
- "Adjusted Lengths: 0\n"
256
- ]
257
- },
258
- {
259
- "name": "stderr",
260
- "output_type": "stream",
261
- "text": [
262
- "\n"
263
- ]
264
- }
265
- ],
266
- "source": [
267
- "def tokenizePrompt(prompt:object) -> dict:\n",
268
- " \"\"\"\n",
269
- " Tokenizes prompt based on prompt and tokenizer.\n",
270
- " \"\"\"\n",
271
- " tokenizedPrompt:dict=tokenizer(formattingFunc(prompt))\n",
272
- " return tokenizedPrompt\n",
273
- "\n",
274
- "# Format and Tokenize datasets.\n",
275
- "tokenizedTrain:dict=trainDataset.map(tokenizePrompt)\n",
276
- "tokenizedVal:dict=evalDataset.map(tokenizePrompt)\n",
277
- "\n",
278
- "# count lengths of both datasets so we can adjust max length\n",
279
- "lengthTokens:list=[len(x['input_ids']) for x in tokenizedTrain] # count lengths of tokenizedTrain\n",
280
- "if tokenizedVal != None:\n",
281
- " lengthTokens += [len(x['input_ids']) for x in tokenizedVal] # count lengths of tokenizedVal\n",
282
- "maxLengthTokens:int=max(lengthTokens) + 2 # we could also visualise lengthTokens using matplotlib if we wish to see the distribution\n",
283
- "tokenDiffOriginal:int=maxLengthTokens-min(lengthTokens) # create metric original\n",
284
- "\n",
285
- "# this function will set all tokens to the same length using left hand padding and the eos token (setup above)\n",
286
- "def tokenizePromptAdjustedLengths(prompt:object):\n",
287
- " \"\"\"\n",
288
- " Tokenizes prompt with adjusted lengths with left handed padding. All sequences will be of the same length which will assist training.\n",
289
- " \"\"\"\n",
290
- " tokenizedResponse = tokenizer(\n",
291
- " formattingFunc(prompt),\n",
292
- " truncation=True,\n",
293
- " max_length=maxLengthTokens,\n",
294
- " padding=\"max_length\",\n",
295
- " )\n",
296
- " return tokenizedResponse\n",
297
- "\n",
298
- "del tokenizedTrain; del tokenizedVal # clean up old variables\n",
299
- "tokenizedTrain:dict=trainDataset.map(tokenizePromptAdjustedLengths) # apply adjusted size tokenization\n",
300
- "tokenizedVal:dict=evalDataset.map(tokenizePromptAdjustedLengths)\n",
301
- "\n",
302
- "# count adjusted size difference\n",
303
- "lengthTokens:list=[len(x['input_ids']) for x in tokenizedTrain] # count lengths of tokenizedTrain\n",
304
- "if tokenizedVal != None:\n",
305
- " lengthTokens += [len(x['input_ids']) for x in tokenizedVal] # count lengths of tokenizedVal\n",
306
- "tokenDiffAdjusted:int=max(lengthTokens)-min(lengthTokens) # create metric adjusted\n",
307
- "\n",
308
- "print(f\"| Diff Token Size |\\nOriginal Lengths: {tokenDiffOriginal}\\nAdjusted Lengths: {tokenDiffAdjusted}\") # compare size differences using metrics from original and adjusted lengths."
309
- ]
310
- },
311
- {
312
- "cell_type": "markdown",
313
- "metadata": {},
314
- "source": [
315
- "#### Get Model Infomation and Set up LoRA layers for finetuning.\n",
316
- "LoRA (Low-Rank Adaptation) is a finetuning technique which freezes the pre-trained model weights and instead interjects trainable matrices into each layer of the Transformer architecture (https://arxiv.org/abs/2106.09685)."
317
- ]
318
- },
319
- {
320
- "cell_type": "code",
321
- "execution_count": 9,
322
- "metadata": {},
323
- "outputs": [
324
- {
325
- "name": "stdout",
326
- "output_type": "stream",
327
- "text": [
328
- "Model Architecture:\n",
329
- "PeftModelForCausalLM(\n",
330
- " (base_model): LoraModel(\n",
331
- " (model): PhiForCausalLM(\n",
332
- " (model): PhiModel(\n",
333
- " (embed_tokens): Embedding(51200, 2560)\n",
334
- " (embed_dropout): Dropout(p=0.0, inplace=False)\n",
335
- " (layers): ModuleList(\n",
336
- " (0-31): 32 x PhiDecoderLayer(\n",
337
- " (self_attn): PhiAttention(\n",
338
- " (q_proj): Linear8bitLt(in_features=2560, out_features=2560, bias=True)\n",
339
- " (k_proj): Linear8bitLt(in_features=2560, out_features=2560, bias=True)\n",
340
- " (v_proj): Linear8bitLt(in_features=2560, out_features=2560, bias=True)\n",
341
- " (dense): Linear8bitLt(in_features=2560, out_features=2560, bias=True)\n",
342
- " (rotary_emb): PhiRotaryEmbedding()\n",
343
- " )\n",
344
- " (mlp): PhiMLP(\n",
345
- " (activation_fn): NewGELUActivation()\n",
346
- " (fc1): lora.Linear8bitLt(\n",
347
- " (base_layer): Linear8bitLt(in_features=2560, out_features=10240, bias=True)\n",
348
- " (lora_dropout): ModuleDict(\n",
349
- " (default): Dropout(p=0.05, inplace=False)\n",
350
- " )\n",
351
- " (lora_A): ModuleDict(\n",
352
- " (default): Linear(in_features=2560, out_features=64, bias=False)\n",
353
- " )\n",
354
- " (lora_B): ModuleDict(\n",
355
- " (default): Linear(in_features=64, out_features=10240, bias=False)\n",
356
- " )\n",
357
- " (lora_embedding_A): ParameterDict()\n",
358
- " (lora_embedding_B): ParameterDict()\n",
359
- " )\n",
360
- " (fc2): lora.Linear8bitLt(\n",
361
- " (base_layer): Linear8bitLt(in_features=10240, out_features=2560, bias=True)\n",
362
- " (lora_dropout): ModuleDict(\n",
363
- " (default): Dropout(p=0.05, inplace=False)\n",
364
- " )\n",
365
- " (lora_A): ModuleDict(\n",
366
- " (default): Linear(in_features=10240, out_features=64, bias=False)\n",
367
- " )\n",
368
- " (lora_B): ModuleDict(\n",
369
- " (default): Linear(in_features=64, out_features=2560, bias=False)\n",
370
- " )\n",
371
- " (lora_embedding_A): ParameterDict()\n",
372
- " (lora_embedding_B): ParameterDict()\n",
373
- " )\n",
374
- " )\n",
375
- " (input_layernorm): LayerNorm((2560,), eps=1e-05, elementwise_affine=True)\n",
376
- " (resid_dropout): Dropout(p=0.1, inplace=False)\n",
377
- " )\n",
378
- " )\n",
379
- " (final_layernorm): LayerNorm((2560,), eps=1e-05, elementwise_affine=True)\n",
380
- " )\n",
381
- " (lm_head): Linear(in_features=2560, out_features=51200, bias=True)\n",
382
- " )\n",
383
- " )\n",
384
- ")\n",
385
- "trainable params: 52,428,800 || all params: 2,832,112,640 || trainable%: 1.8512258043521885\n"
386
- ]
387
- }
388
- ],
389
- "source": [
390
- "loraConfig:object=LoraConfig(\n",
391
- " r=64, # Rank of low-rank matrix, controls the number of parameters trained - a higher rank allowing more parameters to be trained and larger update matrices (and more compute cost). Play with this and see how it effects number of trainable params.\n",
392
- " lora_alpha=16, # LoRA scaing factor of learned weights: alpha/r\n",
393
- " target_modules=[ # modules (eg attention blocks) to apply LoRA matrices.\n",
394
- " \"Wqkv\",\n",
395
- " \"fc1\",\n",
396
- " \"fc2\",\n",
397
- " ],\n",
398
- " bias=\"none\", # should bias parameters also be trained: none, all, lora_only\n",
399
- " lora_dropout=0.05, # Conventional\n",
400
- " task_type=\"CAUSAL_LM\",\n",
401
- ")\n",
402
- "\n",
403
- "model:object=get_peft_model(model, loraConfig) # parameter-efficient fine tune - freeze pretrained model parameters and add small number of tunable adapters on top.\n",
404
- "print(f\"Model Architecture:\\n{model}\")\n",
405
- "model.print_trainable_parameters() # print trainable parameters"
406
- ]
407
- },
408
- {
409
- "cell_type": "markdown",
410
- "metadata": {},
411
- "source": [
412
- "#### Training"
413
- ]
414
- },
415
- {
416
- "cell_type": "code",
417
- "execution_count": 10,
418
- "metadata": {},
419
- "outputs": [
420
- {
421
- "name": "stdout",
422
- "output_type": "stream",
423
- "text": [
424
- "GPU COUNT: 1\n"
425
- ]
426
- }
427
- ],
428
- "source": [
429
- "# Setup train run parameters\n",
430
- "project:str=\"Finetune\"\n",
431
- "modelName:str=baseModelName.replace(\"\\\\\", \"_\").replace(\"/\", \"_\")\n",
432
- "run_name:str=f\"{project}-{modelName}\"\n",
433
- "output_dir:str=\"./\" + run_name # this will be the dir to store run infomation and model weights\n",
434
- "\n",
435
- "# get GPU count for CUDA.\n",
436
- "print(f\"GPU COUNT: {torch.cuda.device_count()}\")\n",
437
- "if torch.cuda.device_count() > 1: # If more than 1 GPU\n",
438
- " model.is_parallelizable = True\n",
439
- " model.model_parallel = True"
440
- ]
441
- },
442
- {
443
- "cell_type": "code",
444
- "execution_count": 11,
445
- "metadata": {},
446
- "outputs": [
447
- {
448
- "name": "stderr",
449
- "output_type": "stream",
450
- "text": [
451
- "/anaconda/envs/phi2ENV/lib/python3.8/site-packages/bitsandbytes/autograd/_functions.py:322: UserWarning: MatMul8bitLt: inputs will be cast from torch.float32 to float16 during quantization\n",
452
- " warnings.warn(f\"MatMul8bitLt: inputs will be cast from {A.dtype} to float16 during quantization\")\n"
453
- ]
454
- },
455
- {
456
- "data": {
457
- "text/html": [
458
- "\n",
459
- " <div>\n",
460
- " \n",
461
- " <progress value='1000' max='1000' style='width:300px; height:20px; vertical-align: middle;'></progress>\n",
462
- " [1000/1000 18:37, Epoch 35/36]\n",
463
- " </div>\n",
464
- " <table border=\"1\" class=\"dataframe\">\n",
465
- " <thead>\n",
466
- " <tr style=\"text-align: left;\">\n",
467
- " <th>Step</th>\n",
468
- " <th>Training Loss</th>\n",
469
- " <th>Validation Loss</th>\n",
470
- " </tr>\n",
471
- " </thead>\n",
472
- " <tbody>\n",
473
- " <tr>\n",
474
- " <td>50</td>\n",
475
- " <td>2.162500</td>\n",
476
- " <td>2.003207</td>\n",
477
- " </tr>\n",
478
- " <tr>\n",
479
- " <td>100</td>\n",
480
- " <td>1.779000</td>\n",
481
- " <td>1.718166</td>\n",
482
- " </tr>\n",
483
- " <tr>\n",
484
- " <td>150</td>\n",
485
- " <td>1.593500</td>\n",
486
- " <td>1.527336</td>\n",
487
- " </tr>\n",
488
- " <tr>\n",
489
- " <td>200</td>\n",
490
- " <td>1.382200</td>\n",
491
- " <td>1.365474</td>\n",
492
- " </tr>\n",
493
- " <tr>\n",
494
- " <td>250</td>\n",
495
- " <td>1.283400</td>\n",
496
- " <td>1.209522</td>\n",
497
- " </tr>\n",
498
- " <tr>\n",
499
- " <td>300</td>\n",
500
- " <td>1.150800</td>\n",
501
- " <td>1.050137</td>\n",
502
- " </tr>\n",
503
- " <tr>\n",
504
- " <td>350</td>\n",
505
- " <td>1.016200</td>\n",
506
- " <td>0.932797</td>\n",
507
- " </tr>\n",
508
- " <tr>\n",
509
- " <td>400</td>\n",
510
- " <td>0.947200</td>\n",
511
- " <td>0.808629</td>\n",
512
- " </tr>\n",
513
- " <tr>\n",
514
- " <td>450</td>\n",
515
- " <td>0.815600</td>\n",
516
- " <td>0.709524</td>\n",
517
- " </tr>\n",
518
- " <tr>\n",
519
- " <td>500</td>\n",
520
- " <td>0.757900</td>\n",
521
- " <td>0.616750</td>\n",
522
- " </tr>\n",
523
- " <tr>\n",
524
- " <td>550</td>\n",
525
- " <td>0.663500</td>\n",
526
- " <td>0.544908</td>\n",
527
- " </tr>\n",
528
- " <tr>\n",
529
- " <td>600</td>\n",
530
- " <td>0.612800</td>\n",
531
- " <td>0.482578</td>\n",
532
- " </tr>\n",
533
- " <tr>\n",
534
- " <td>650</td>\n",
535
- " <td>0.604100</td>\n",
536
- " <td>0.434236</td>\n",
537
- " </tr>\n",
538
- " <tr>\n",
539
- " <td>700</td>\n",
540
- " <td>0.513200</td>\n",
541
- " <td>0.397516</td>\n",
542
- " </tr>\n",
543
- " <tr>\n",
544
- " <td>750</td>\n",
545
- " <td>0.484000</td>\n",
546
- " <td>0.360740</td>\n",
547
- " </tr>\n",
548
- " <tr>\n",
549
- " <td>800</td>\n",
550
- " <td>0.458000</td>\n",
551
- " <td>0.340060</td>\n",
552
- " </tr>\n",
553
- " <tr>\n",
554
- " <td>850</td>\n",
555
- " <td>0.453700</td>\n",
556
- " <td>0.317527</td>\n",
557
- " </tr>\n",
558
- " <tr>\n",
559
- " <td>900</td>\n",
560
- " <td>0.426400</td>\n",
561
- " <td>0.308004</td>\n",
562
- " </tr>\n",
563
- " <tr>\n",
564
- " <td>950</td>\n",
565
- " <td>0.417900</td>\n",
566
- " <td>0.297917</td>\n",
567
- " </tr>\n",
568
- " <tr>\n",
569
- " <td>1000</td>\n",
570
- " <td>0.422100</td>\n",
571
- " <td>0.298028</td>\n",
572
- " </tr>\n",
573
- " </tbody>\n",
574
- "</table><p>"
575
- ],
576
- "text/plain": [
577
- "<IPython.core.display.HTML object>"
578
- ]
579
- },
580
- "metadata": {},
581
- "output_type": "display_data"
582
- },
583
- {
584
- "name": "stderr",
585
- "output_type": "stream",
586
- "text": [
587
- "Checkpoint destination directory ./Finetune-microsoft_phi-2/checkpoint-50 already exists and is non-empty.Saving will proceed but saved results may be invalid.\n",
588
- "/anaconda/envs/phi2ENV/lib/python3.8/site-packages/bitsandbytes/autograd/_functions.py:322: UserWarning: MatMul8bitLt: inputs will be cast from torch.float32 to float16 during quantization\n",
589
- " warnings.warn(f\"MatMul8bitLt: inputs will be cast from {A.dtype} to float16 during quantization\")\n",
590
- "Checkpoint destination directory ./Finetune-microsoft_phi-2/checkpoint-100 already exists and is non-empty.Saving will proceed but saved results may be invalid.\n",
591
- "/anaconda/envs/phi2ENV/lib/python3.8/site-packages/bitsandbytes/autograd/_functions.py:322: UserWarning: MatMul8bitLt: inputs will be cast from torch.float32 to float16 during quantization\n",
592
- " warnings.warn(f\"MatMul8bitLt: inputs will be cast from {A.dtype} to float16 during quantization\")\n",
593
- "Checkpoint destination directory ./Finetune-microsoft_phi-2/checkpoint-150 already exists and is non-empty.Saving will proceed but saved results may be invalid.\n",
594
- "/anaconda/envs/phi2ENV/lib/python3.8/site-packages/bitsandbytes/autograd/_functions.py:322: UserWarning: MatMul8bitLt: inputs will be cast from torch.float32 to float16 during quantization\n",
595
- " warnings.warn(f\"MatMul8bitLt: inputs will be cast from {A.dtype} to float16 during quantization\")\n",
596
- "Checkpoint destination directory ./Finetune-microsoft_phi-2/checkpoint-200 already exists and is non-empty.Saving will proceed but saved results may be invalid.\n",
597
- "/anaconda/envs/phi2ENV/lib/python3.8/site-packages/bitsandbytes/autograd/_functions.py:322: UserWarning: MatMul8bitLt: inputs will be cast from torch.float32 to float16 during quantization\n",
598
- " warnings.warn(f\"MatMul8bitLt: inputs will be cast from {A.dtype} to float16 during quantization\")\n",
599
- "Checkpoint destination directory ./Finetune-microsoft_phi-2/checkpoint-250 already exists and is non-empty.Saving will proceed but saved results may be invalid.\n",
600
- "/anaconda/envs/phi2ENV/lib/python3.8/site-packages/bitsandbytes/autograd/_functions.py:322: UserWarning: MatMul8bitLt: inputs will be cast from torch.float32 to float16 during quantization\n",
601
- " warnings.warn(f\"MatMul8bitLt: inputs will be cast from {A.dtype} to float16 during quantization\")\n",
602
- "Checkpoint destination directory ./Finetune-microsoft_phi-2/checkpoint-300 already exists and is non-empty.Saving will proceed but saved results may be invalid.\n",
603
- "/anaconda/envs/phi2ENV/lib/python3.8/site-packages/bitsandbytes/autograd/_functions.py:322: UserWarning: MatMul8bitLt: inputs will be cast from torch.float32 to float16 during quantization\n",
604
- " warnings.warn(f\"MatMul8bitLt: inputs will be cast from {A.dtype} to float16 during quantization\")\n",
605
- "Checkpoint destination directory ./Finetune-microsoft_phi-2/checkpoint-350 already exists and is non-empty.Saving will proceed but saved results may be invalid.\n",
606
- "/anaconda/envs/phi2ENV/lib/python3.8/site-packages/bitsandbytes/autograd/_functions.py:322: UserWarning: MatMul8bitLt: inputs will be cast from torch.float32 to float16 during quantization\n",
607
- " warnings.warn(f\"MatMul8bitLt: inputs will be cast from {A.dtype} to float16 during quantization\")\n",
608
- "Checkpoint destination directory ./Finetune-microsoft_phi-2/checkpoint-400 already exists and is non-empty.Saving will proceed but saved results may be invalid.\n",
609
- "/anaconda/envs/phi2ENV/lib/python3.8/site-packages/bitsandbytes/autograd/_functions.py:322: UserWarning: MatMul8bitLt: inputs will be cast from torch.float32 to float16 during quantization\n",
610
- " warnings.warn(f\"MatMul8bitLt: inputs will be cast from {A.dtype} to float16 during quantization\")\n",
611
- "Checkpoint destination directory ./Finetune-microsoft_phi-2/checkpoint-450 already exists and is non-empty.Saving will proceed but saved results may be invalid.\n",
612
- "/anaconda/envs/phi2ENV/lib/python3.8/site-packages/bitsandbytes/autograd/_functions.py:322: UserWarning: MatMul8bitLt: inputs will be cast from torch.float32 to float16 during quantization\n",
613
- " warnings.warn(f\"MatMul8bitLt: inputs will be cast from {A.dtype} to float16 during quantization\")\n",
614
- "Checkpoint destination directory ./Finetune-microsoft_phi-2/checkpoint-500 already exists and is non-empty.Saving will proceed but saved results may be invalid.\n",
615
- "/anaconda/envs/phi2ENV/lib/python3.8/site-packages/bitsandbytes/autograd/_functions.py:322: UserWarning: MatMul8bitLt: inputs will be cast from torch.float32 to float16 during quantization\n",
616
- " warnings.warn(f\"MatMul8bitLt: inputs will be cast from {A.dtype} to float16 during quantization\")\n",
617
- "Checkpoint destination directory ./Finetune-microsoft_phi-2/checkpoint-550 already exists and is non-empty.Saving will proceed but saved results may be invalid.\n",
618
- "/anaconda/envs/phi2ENV/lib/python3.8/site-packages/bitsandbytes/autograd/_functions.py:322: UserWarning: MatMul8bitLt: inputs will be cast from torch.float32 to float16 during quantization\n",
619
- " warnings.warn(f\"MatMul8bitLt: inputs will be cast from {A.dtype} to float16 during quantization\")\n",
620
- "Checkpoint destination directory ./Finetune-microsoft_phi-2/checkpoint-600 already exists and is non-empty.Saving will proceed but saved results may be invalid.\n",
621
- "/anaconda/envs/phi2ENV/lib/python3.8/site-packages/bitsandbytes/autograd/_functions.py:322: UserWarning: MatMul8bitLt: inputs will be cast from torch.float32 to float16 during quantization\n",
622
- " warnings.warn(f\"MatMul8bitLt: inputs will be cast from {A.dtype} to float16 during quantization\")\n",
623
- "Checkpoint destination directory ./Finetune-microsoft_phi-2/checkpoint-650 already exists and is non-empty.Saving will proceed but saved results may be invalid.\n",
624
- "/anaconda/envs/phi2ENV/lib/python3.8/site-packages/bitsandbytes/autograd/_functions.py:322: UserWarning: MatMul8bitLt: inputs will be cast from torch.float32 to float16 during quantization\n",
625
- " warnings.warn(f\"MatMul8bitLt: inputs will be cast from {A.dtype} to float16 during quantization\")\n",
626
- "Checkpoint destination directory ./Finetune-microsoft_phi-2/checkpoint-700 already exists and is non-empty.Saving will proceed but saved results may be invalid.\n",
627
- "/anaconda/envs/phi2ENV/lib/python3.8/site-packages/bitsandbytes/autograd/_functions.py:322: UserWarning: MatMul8bitLt: inputs will be cast from torch.float32 to float16 during quantization\n",
628
- " warnings.warn(f\"MatMul8bitLt: inputs will be cast from {A.dtype} to float16 during quantization\")\n",
629
- "/anaconda/envs/phi2ENV/lib/python3.8/site-packages/bitsandbytes/autograd/_functions.py:322: UserWarning: MatMul8bitLt: inputs will be cast from torch.float32 to float16 during quantization\n",
630
- " warnings.warn(f\"MatMul8bitLt: inputs will be cast from {A.dtype} to float16 during quantization\")\n",
631
- "/anaconda/envs/phi2ENV/lib/python3.8/site-packages/bitsandbytes/autograd/_functions.py:322: UserWarning: MatMul8bitLt: inputs will be cast from torch.float32 to float16 during quantization\n",
632
- " warnings.warn(f\"MatMul8bitLt: inputs will be cast from {A.dtype} to float16 during quantization\")\n",
633
- "/anaconda/envs/phi2ENV/lib/python3.8/site-packages/bitsandbytes/autograd/_functions.py:322: UserWarning: MatMul8bitLt: inputs will be cast from torch.float32 to float16 during quantization\n",
634
- " warnings.warn(f\"MatMul8bitLt: inputs will be cast from {A.dtype} to float16 during quantization\")\n",
635
- "/anaconda/envs/phi2ENV/lib/python3.8/site-packages/bitsandbytes/autograd/_functions.py:322: UserWarning: MatMul8bitLt: inputs will be cast from torch.float32 to float16 during quantization\n",
636
- " warnings.warn(f\"MatMul8bitLt: inputs will be cast from {A.dtype} to float16 during quantization\")\n",
637
- "/anaconda/envs/phi2ENV/lib/python3.8/site-packages/bitsandbytes/autograd/_functions.py:322: UserWarning: MatMul8bitLt: inputs will be cast from torch.float32 to float16 during quantization\n",
638
- " warnings.warn(f\"MatMul8bitLt: inputs will be cast from {A.dtype} to float16 during quantization\")\n"
639
- ]
640
- },
641
- {
642
- "data": {
643
- "text/plain": [
644
- "TrainOutput(global_step=1000, training_loss=0.8971948947906494, metrics={'train_runtime': 1119.2081, 'train_samples_per_second': 1.787, 'train_steps_per_second': 0.893, 'total_flos': 9658921328640000.0, 'train_loss': 0.8971948947906494, 'epoch': 35.71})"
645
- ]
646
- },
647
- "execution_count": 11,
648
- "metadata": {},
649
- "output_type": "execute_result"
650
- }
651
- ],
652
- "source": [
653
- "stepsSaveEvalLoss:int=50\n",
654
- "numberStepPartitions:int=20 # stepsSaveEvalLoss muliplied by numberStepPartitions gets max_steps - done so that the last step is always a multiple of stepsSaveEvalLoss and it saves.\n",
655
- "max_steps:int=stepsSaveEvalLoss*numberStepPartitions\n",
656
- "trainer:object=Trainer(\n",
657
- " model=model,\n",
658
- " train_dataset=tokenizedTrain,\n",
659
- " eval_dataset=tokenizedVal,\n",
660
- " args=TrainingArguments(\n",
661
- " output_dir=output_dir, # output dir defined above\n",
662
- " warmup_steps=1, # number of steps for the warmup phase where the learning rate is gradually increased from a low value to the maximum value where normal schedule begins - can improve the stability and performance.\n",
663
- " per_device_train_batch_size=2, # specifies the batch size per device for training. It should be an integer that is greater than zero.\n",
664
- " gradient_accumulation_steps=1, # specifies the number of steps to accumulate gradients before performing a backward and an optimizer step. It should be an integer that is greater than zero. The effective batch size is the product of this argument and the per_device_train_batch_size\n",
665
- " max_steps=max_steps, # max number of training steps\n",
666
- " learning_rate=2.5e-5, # aim for small LR for finetuning scenarios\n",
667
- " optim=\"paged_adamw_8bit\", # optimiser type to adjust LR during training\n",
668
- " logging_dir=f\"{output_dir}/logs\", # Where logs are stored for training\n",
669
- " logging_steps=stepsSaveEvalLoss, # train loss cadence\n",
670
- " do_eval=True, # perform eval on eval set\n",
671
- " evaluation_strategy=\"steps\", # eval model loss set to steps\n",
672
- " eval_steps=stepsSaveEvalLoss, # eval loss cadence\n",
673
- " save_strategy=\"steps\", # checkpoint model progress strategy set to steps\n",
674
- " save_steps=stepsSaveEvalLoss, # save every x steps cadence\n",
675
- " ),\n",
676
- " data_collator=DataCollatorForLanguageModeling(tokenizer, mlm=False), # mlm - masked language modeling\n",
677
- ")\n",
678
- "model.config.use_cache = False # silence warnings for training\n",
679
- "\n",
680
- "# Train - The output should be a table with a row at stepsSaveEvalLoss cadence and columns as Step, Training loss and Validation Loss.\n",
681
- "trainer.train()"
682
- ]
683
- },
684
- {
685
- "cell_type": "markdown",
686
- "metadata": {},
687
- "source": [
688
- "#### Inference of trained model"
689
- ]
690
- },
691
- {
692
- "cell_type": "markdown",
693
- "metadata": {},
694
- "source": [
695
- "Kill the GPU process to completely clear memory:\n",
696
- "\n",
697
- " nvidia smi > kill [PID]\n",
698
- "OR\n",
699
- "\n",
700
- " Kernel > Restart Kernel"
701
- ]
702
- },
703
- {
704
- "cell_type": "code",
705
- "execution_count": 1,
706
- "metadata": {},
707
- "outputs": [
708
- {
709
- "name": "stderr",
710
- "output_type": "stream",
711
- "text": [
712
- "/anaconda/envs/phi2ENV/lib/python3.8/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
713
- " from .autonotebook import tqdm as notebook_tqdm\n"
714
- ]
715
- }
716
- ],
717
- "source": [
718
- "# Empty VRAM and clear model, trainer variables\n",
719
- "try: \n",
720
- " del model\n",
721
- " del tokenizer\n",
722
- " del trainer\n",
723
- " import gc\n",
724
- " gc.collect()\n",
725
- "except:\n",
726
- " pass\n",
727
- "\n",
728
- "# load libraries for inference\n",
729
- "import torch\n",
730
- "from transformers import AutoTokenizer, AutoModelForCausalLM\n",
731
- "from peft import PeftModel\n",
732
- "\n",
733
- "# memory cleared so recreate parameters\n",
734
- "baseModelName:str=\"microsoft/phi-2\"\n",
735
- "project:str=\"Finetune\"\n",
736
- "max_steps:int=1000\n",
737
- "\n",
738
- "modelName:str=baseModelName.replace(\"\\\\\", \"_\").replace(\"/\", \"_\")\n",
739
- "run_name:str=f\"{project}-{modelName}\"\n",
740
- "output_dir:str=\"./\" + run_name # this will be the dir to store run infomation and model weights"
741
- ]
742
- },
743
- {
744
- "cell_type": "code",
745
- "execution_count": 4,
746
- "metadata": {},
747
- "outputs": [
748
- {
749
- "name": "stderr",
750
- "output_type": "stream",
751
- "text": [
752
- "Loading checkpoint shards: 100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████��█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 2/2 [00:06<00:00, 3.32s/it]\n",
753
- "Special tokens have been added in the vocabulary, make sure the associated word embeddings are fine-tuned or trained.\n"
754
- ]
755
- }
756
- ],
757
- "source": [
758
- "# reload our base model and tokeniser\n",
759
- "modelInference:object=AutoModelForCausalLM.from_pretrained(\n",
760
- " baseModelName, # Phi2, same as before\n",
761
- " torch_dtype=torch.float32, # fixes issue in inference related to float16 values producing \"!!!!\" rather than output.\n",
762
- " device_map=\"auto\", \n",
763
- " trust_remote_code=True,\n",
764
- " load_in_8bit=True,\n",
765
- ")\n",
766
- "tokenizerInference:object=AutoTokenizer.from_pretrained(baseModelName,\n",
767
- " add_bos_token=True,\n",
768
- " trust_remote_code=True,\n",
769
- " use_fast=False)\n",
770
- "tokenizerInference.pad_token = tokenizerInference.eos_token\n",
771
- "\n",
772
- "# load finetuned QLoRA adapters which were saved during training\n",
773
- "finetunedFolder:str=f\"{output_dir}/checkpoint-{max_steps}\" # get latest model by default (can change if you see better performance on other models)\n",
774
- "FTmodel:object=PeftModel.from_pretrained(modelInference, finetunedFolder) # load FT model"
775
- ]
776
- },
777
- {
778
- "cell_type": "markdown",
779
- "metadata": {},
780
- "source": [
781
- "We can play with the repetition penalty, which can influence the likelihood of repeated content. A higher repetition penalty makes the model less likely to generate repeated phrases or words in the text, while a lower repetition penalty allows more repetition.\n",
782
- "\n",
783
- "!Note if an issue persists where \"!!!!\" is produced instead of text output from the model it is related to an issue setting torch_dtype=torch.float16 rather than torch.float32 when loading the model. See here for more details: https://huggingface.co/microsoft/phi-2/discussions/89\n"
784
- ]
785
- },
786
- {
787
- "cell_type": "code",
788
- "execution_count": 5,
789
- "metadata": {},
790
- "outputs": [
791
- {
792
- "name": "stderr",
793
- "output_type": "stream",
794
- "text": [
795
- "Setting `pad_token_id` to `eos_token_id`:50256 for open-end generation.\n",
796
- "/anaconda/envs/phi2ENV/lib/python3.8/site-packages/bitsandbytes/autograd/_functions.py:322: UserWarning: MatMul8bitLt: inputs will be cast from torch.float32 to float16 during quantization\n",
797
- " warnings.warn(f\"MatMul8bitLt: inputs will be cast from {A.dtype} to float16 during quantization\")\n"
798
- ]
799
- },
800
- {
801
- "name": "stdout",
802
- "output_type": "stream",
803
- "text": [
804
- "question: How do I install Microsoft 365 or Office?\n",
805
- "answer:  [Install Office](https://go.microsoft.com/fwlink/p/?LinkID=403719) and [Install Microsoft 365](https://go.microsoft.com/fwlink/p/?LinkID=808164) are the best ways to install Microsoft 365 or Office. You can also download and install older versions of Office on PC or Mac for free. Learn more about installing Office apps. [Office Home & Business](https://go.microsoft.com/fwlink/p/?LinkID=808164) and [Office Home & Student](https://go.microsoft.com/fwlink/p/?LinkID=808164) are subscription plans that include the Office apps, along with additional features. Learn more about Microsoft 365 subscriptions. [Office for Mac](https://go.microsoft.com/fwlink/p/?LinkID=808164) and [Office for Windows tablets](https://\n"
806
- ]
807
- }
808
- ],
809
- "source": [
810
- "# model hyperparameters\n",
811
- "repetition_penalty:float=1.0\n",
812
- "max_tokens:int=200\n",
813
- "\n",
814
- "# test a prompt\n",
815
- "testPrompt:str=\"How do I install Microsoft 365 or Office?\"\n",
816
- "\n",
817
- "formattedPrompt:str=f\"question: {testPrompt}\\nanswer: \" # format like training set formatting, see above.\n",
818
- "tokenisedPrompt:dict=tokenizerInference(formattedPrompt, return_tensors=\"pt\").to(\"cuda\") # tokenise prompt\n",
819
- "FTmodel.eval() # set in inference mode\n",
820
- "with torch.no_grad():\n",
821
- " response:str=tokenizerInference.decode(FTmodel.generate(**tokenisedPrompt, max_new_tokens=max_tokens, repetition_penalty=repetition_penalty)[0], skip_special_tokens=True)\n",
822
- " print(response)"
823
- ]
824
- }
825
- ],
826
- "metadata": {
827
- "kernel_info": {
828
- "name": "python3"
829
- },
830
- "kernelspec": {
831
- "display_name": "Python 3 (ipykernel)",
832
- "language": "python",
833
- "name": "python3"
834
- },
835
- "language_info": {
836
- "codemirror_mode": {
837
- "name": "ipython",
838
- "version": 3
839
- },
840
- "file_extension": ".py",
841
- "mimetype": "text/x-python",
842
- "name": "python",
843
- "nbconvert_exporter": "python",
844
- "pygments_lexer": "ipython3",
845
- "version": "3.8.18"
846
- },
847
- "microsoft": {
848
- "ms_spell_check": {
849
- "ms_spell_check_language": "en"
850
- }
851
- },
852
- "nteract": {
853
- "version": "nteract-front-end@1.0.0"
854
- }
855
- },
856
- "nbformat": 4,
857
- "nbformat_minor": 2
858
- }