diff --git a/.github/workflows/build_documentation.yml b/.github/workflows/build_documentation.yml index 3aed3267..9ee5d75b 100644 --- a/.github/workflows/build_documentation.yml +++ b/.github/workflows/build_documentation.yml @@ -17,7 +17,7 @@ jobs: package_name: cookbook path_to_docs: cookbook/notebooks/ additional_args: --not_python_module - languages: en zh-CN + languages: en zh-CN fa convert_notebooks: true secrets: hf_token: ${{ secrets.HF_DOC_BUILD_PUSH }} \ No newline at end of file diff --git a/.github/workflows/build_pr_documentation.yml b/.github/workflows/build_pr_documentation.yml index 64aaf9fe..c46098f8 100644 --- a/.github/workflows/build_pr_documentation.yml +++ b/.github/workflows/build_pr_documentation.yml @@ -20,5 +20,5 @@ jobs: package_name: cookbook path_to_docs: cookbook/notebooks/ additional_args: --not_python_module - languages: en zh-CN + languages: en zh-CN fa convert_notebooks: true \ No newline at end of file diff --git a/notebooks/fa/_toctree.yml b/notebooks/fa/_toctree.yml new file mode 100644 index 00000000..e84e18c4 --- /dev/null +++ b/notebooks/fa/_toctree.yml @@ -0,0 +1,7 @@ +- title: کتابچه متن-باز هوش مصنوعی + sections: + - local: index + title: کتابچه متن-باز هوش مصنوعی + - local: fine_tuning_code_llm_on_single_gpu + title: تنظیم دقیق مدل زبانی بزرگ مبتنی بر کد با کدهای شخصی توسط یک هسته GPU + \ No newline at end of file diff --git a/notebooks/fa/fine_tuning_code_llm_on_single_gpu.ipynb b/notebooks/fa/fine_tuning_code_llm_on_single_gpu.ipynb new file mode 100644 index 00000000..dddc04b5 --- /dev/null +++ b/notebooks/fa/fine_tuning_code_llm_on_single_gpu.ipynb @@ -0,0 +1,1127 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "FNdZ-kD0l78P" + }, + "source": [ + "# تنظیم دقیق مدل زبانی بزرگ مبتنی بر کد با کدهای شخصی توسط یک هسته GPU\n", + "\n", + "_نویسنده: [Maria Khalusova](https://github.com/MKhalusova)_\n", + "\n", + "مدلهای زبانی بزرگ (LLM) عمومیِ مبتنی بر کد از جمله Codex ،StarCoder و CodeLlama در تولید کدهایی که از اصول کلی برنامه نویسی و سینتکس برنامهنویسی پیروی می کنند عالی هستند، اما ممکن است با قراردادهای داخلی سازمان مطابقت نداشته باشند یا از کتابخانه های اختصاصی مطلع نباشند.\n", + "\n", + "در این دفترچه، نحوه تنظیم دقیق (fine-tune) یک LLM مبتنی بر کد، بر روی کدهای خصوصی را برای بهبود فهم بافتاری آن در متن و افزایش کاربرد مدل برای نیازهای به خصوص سازمان شما نشان خواهیم داد. از آنجایی که LLM های مبتنی بر کد بسیار بزرگ هستند، تنظیم دقیق آنها به روش سنتی میتواند از نظر منابع پرمصرف باشد. نگران نباشید! ما نحوه بهینه سازی تنظیم دقیق را بر روی یک هسته GPU نشان خواهیم داد.\n", + "\n", + "## دیتاست\n", + "\n", + "برای این مثال، ما ۱۰ ریپازیتوری عمومی برتر Hugging Face را از Github انتخاب کردیم. ما فایلهای غیر کد مانند تصاویر، فایلهای صوتی، ارائهها و غیره را از دادهها حذف کردهایم. برای Jupyter Notebook ها، ما فقط سلولهایی را که حاوی کد هستند نگه داشتهایم. کد حاصل به عنوان دیتاستی ذخیره میشود که میتوانید آن را در Hugging Face Hub تحت [`smangrul/hf-stack-v1`](https://huggingface.co/datasets/smangrul/hf-stack-v1) پیدا کنید. این دیتاست شامل شناسه ریپازیتوری، مسیر فایل و محتوای فایل است. \n", + "\n", + "\n", + "## مدل\n", + "\n", + "ما مدل [`bigcode/starcoderbase-1b`](https://huggingface.co/bigcode/starcoderbase-1b) که یک مدل با یک میلیارد پارامتر است و روی بیش از ۸۰ زبان برنامه نویسی آموزش دیده است را تنظیم دقیق میکنیم. دسترسی به این مدل محدود است، بنابرین اگر قصد دارید این دفترچه را با همین مدل اجرا کنید، باید از صفحه مدل به آن دسترسی پیدا کنید. برای انجام این کار، با حساب کاربری Hugging Face خود وارد شوید." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "bPlCJYDK6vrF" + }, + "outputs": [], + "source": [ + "from huggingface_hub import notebook_login\n", + "\n", + "notebook_login()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "WMVe_c8q43Qo" + }, + "source": [ + "برای شروع، بیایید تمام کتابخانههای ضروری را نصب کنیم. همانطور که میبینید، علاوه بر `transformers` و `datasets`، از `peft`، `bitsandbytes` و `flash-attn` برای بهینهسازی آموزش مدل استفاده خواهیم کرد.\n", + "\n", + "با استفاده از تکنیکهای آموزش کارآمد پارامتر، میتوانیم این دفترچه را روی یک GPU A100 با رم بالا اجرا کنیم." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "Fp7i8WMCjKJG" + }, + "outputs": [], + "source": [ + "!pip install -q transformers datasets peft bitsandbytes flash-attn" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "16EdABzt3_Ig" + }, + "source": [ + "اکنون چند متغیر تعریف میکنیم. در بازی کردن با این متغیرها و تعویض مقدار آنها راحت باشید." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "hru3G-CLmqis" + }, + "outputs": [], + "source": [ + "MODEL=\"bigcode/starcoderbase-1b\" # Model checkpoint on the Hugging Face Hub\n", + "DATASET=\"smangrul/hf-stack-v1\" # Dataset on the Hugging Face Hub\n", + "DATA_COLUMN=\"content\" # Column name containing the code content\n", + "\n", + "SEQ_LENGTH=2048 # Sequence length\n", + "\n", + "# Training arguments\n", + "MAX_STEPS=2000 # max_steps\n", + "BATCH_SIZE=16 # batch_size\n", + "GR_ACC_STEPS=1 # gradient_accumulation_steps\n", + "LR=5e-4 # learning_rate\n", + "LR_SCHEDULER_TYPE=\"cosine\" # lr_scheduler_type\n", + "WEIGHT_DECAY=0.01 # weight_decay\n", + "NUM_WARMUP_STEPS=30 # num_warmup_steps\n", + "EVAL_FREQ=100 # eval_freq\n", + "SAVE_FREQ=100 # save_freq\n", + "LOG_FREQ=25 # log_freq\n", + "OUTPUT_DIR=\"peft-starcoder-lora-a100\" # output_dir\n", + "BF16=True # bf16\n", + "FP16=False # no_fp16\n", + "\n", + "# FIM trasformations arguments\n", + "FIM_RATE=0.5 # fim_rate\n", + "FIM_SPM_RATE=0.5 # fim_spm_rate\n", + "\n", + "# LORA\n", + "LORA_R=8 # lora_r\n", + "LORA_ALPHA=32 # lora_alpha\n", + "LORA_DROPOUT=0.0 # lora_dropout\n", + "LORA_TARGET_MODULES=\"c_proj,c_attn,q_attn,c_fc,c_proj\" # lora_target_modules\n", + "\n", + "# bitsandbytes config\n", + "USE_NESTED_QUANT=True # use_nested_quant\n", + "BNB_4BIT_COMPUTE_DTYPE=\"bfloat16\"# bnb_4bit_compute_dtype\n", + "\n", + "SEED=0" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "FyZSXTbJrcnC" + }, + "outputs": [], + "source": [ + "from transformers import (\n", + " AutoModelForCausalLM,\n", + " AutoTokenizer,\n", + " Trainer,\n", + " TrainingArguments,\n", + " logging,\n", + " set_seed,\n", + " BitsAndBytesConfig,\n", + ")\n", + "\n", + "set_seed(SEED)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "pO7F5L5AtKo1" + }, + "source": [ + "## آمادهسازی دادهها" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "1LmrIZqP0oUE" + }, + "source": [ + "با بارگذاری داده شروع کنید. از آنجایی که به احتمال زیاد دیتاست بسیار بزرگ است، حتما حالت استریم (streaming) را فعال کنید. استریم به ما این امکان را میدهد که دادهها را به تدریج در حالی که روی دیتاست تکرار میکنیم، بارگذاری کنیم به جای اینکه کل دیتاست را به طور یکجا دانلود کنیم.\n", + "\n", + "ما ۴۰۰۰ نمونه اول را به عنوان دیتاست اعتبارسنجی (validation set) اختصاص خواهیم داد و باقیمانده به عنوان دادههای آموزشی در نظر گرفته میشود." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "4oJZvZb-1J88" + }, + "outputs": [], + "source": [ + "from datasets import load_dataset\n", + "import torch\n", + "from tqdm import tqdm\n", + "\n", + "\n", + "dataset = load_dataset(\n", + " DATASET,\n", + " data_dir=\"data\",\n", + " split=\"train\",\n", + " streaming=True,\n", + ")\n", + "\n", + "valid_data = dataset.take(4000)\n", + "train_data = dataset.skip(4000)\n", + "train_data = train_data.shuffle(buffer_size=5000, seed=SEED)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "sLQ8t0LM2GR6" + }, + "source": [ + "**تبدیلات اختیاری FIM (اختیاری - مدل پرکردن میانی)**\n", + "\n", + "مدلهای زبان خودبازگشت (Autoregressive) معمولا توالیها را از چپ به راست تولید میکنند. با اعمال تبدیلات FIM، مدل همچنین میتواند یاد بگیرد که متن را در بخشهای میانی پر کند. برای اطلاعات بیشتر در مورد این تکنیک، به مقالهی [Efficient Training of Language Models to Fill in the Middle](https://arxiv.org/pdf/2207.14255.pdf) مراجعه کنید.\n", + "\n", + "ما در اینجا تبدیلات FIM را تعریف میکنیم و هنگام ایجاد دیتاست قابل تکرار (Iterable Dataset) از آنها استفاده خواهیم کرد. با این حال، اگر میخواهید تبدیلی صورت نگیرد، میتوانید پارامتر fim_rate را روی ۰ تنظیم کنید." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "KCiAvydztNsu", + "outputId": "cabf7fd0-a922-4371-cbc6-60ee99ef7469" + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "100%|██████████| 400/400 [00:10<00:00, 39.87it/s] " + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The character to token ratio of the dataset is: 2.43\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "tokenizer = AutoTokenizer.from_pretrained(MODEL, trust_remote_code=True)\n", + "\n", + "def chars_token_ratio(dataset, tokenizer, data_column, nb_examples=400):\n", + " \"\"\"\n", + " Estimate the average number of characters per token in the dataset.\n", + " \"\"\"\n", + "\n", + " total_characters, total_tokens = 0, 0\n", + " for _, example in tqdm(zip(range(nb_examples), iter(dataset)), total=nb_examples):\n", + " total_characters += len(example[data_column])\n", + " total_tokens += len(tokenizer(example[data_column]).tokens())\n", + "\n", + " return total_characters / total_tokens\n", + "\n", + "\n", + "chars_per_token = chars_token_ratio(train_data, tokenizer, DATA_COLUMN)\n", + "print(f\"The character to token ratio of the dataset is: {chars_per_token:.2f}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "6F13VGobB3Ma" + }, + "source": [ + "نسبت کاراکتر به توکن همچنین میتواند به عنوان شاخصی از کیفیت توکنیزهکردن متن استفاده شود. برای مثال، نسبت کاراکتر به توکن ۱٫۰ به این معنی است که هر کاراکتر با یک توکن نمایش داده میشود، که چندان معنیدار نیست. این نشاندهنده توکنیزهکردن ضعیف است. در متن استاندارد انگلیسی، یک توکن معمولاً معادل تقریباً چهار کاراکتر است، یعنی نسبت کاراکتر به توکن حدود ۴٫۰ است. در دیتاست کد انتظار داریم این نسبت کمتر باشد، اما به طور کلی، عددی بین ۲٫۰ تا ۳٫۵ را می توان به اندازه کافی خوب در نظر گرفت." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "rcwYFRPpwxea" + }, + "source": [ + "**تبدیلات FIM (اختیاری)**\n", + "\n", + "\n", + "مدلهای زبان خودبازگشت (Autoregressive) معمولا توالیها را از چپ به راست تولید میکنند. با اعمال تبدیلات FIM، مدل همچنین میتواند یاد بگیرد که متن را در بخشهای میانی پر کند. برای اطلاعات بیشتر در مورد این تکنیک، به مقالهی \" [آموزش کارآمد مدلهای زبان برای پر کردن بخش میان](https://arxiv.org/pdf/2207.14255.pdf) \"مراجعه کنید.\n", + "\n", + "ما در اینجا تبدیلات FIM را تعریف میکنیم و هنگام ایجاد دیتاست قابل تکرار (Iterable Dataset) از آنها استفاده خواهیم کرد. با این حال، اگر میخواهید تبدیلی صورت نگیرد، میتوانید پارامتر fim_rate را روی ۰ تنظیم کنید." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "zmejYvEKw1E-" + }, + "outputs": [], + "source": [ + "import functools\n", + "import numpy as np\n", + "\n", + "\n", + "# Helper function to get token ids of the special tokens for prefix, suffix and middle for FIM transformations.\n", + "@functools.lru_cache(maxsize=None)\n", + "def get_fim_token_ids(tokenizer):\n", + " try:\n", + " FIM_PREFIX, FIM_MIDDLE, FIM_SUFFIX, FIM_PAD = tokenizer.special_tokens_map[\"additional_special_tokens\"][1:5]\n", + " suffix_tok_id, prefix_tok_id, middle_tok_id, pad_tok_id = (\n", + " tokenizer.vocab[tok] for tok in [FIM_SUFFIX, FIM_PREFIX, FIM_MIDDLE, FIM_PAD]\n", + " )\n", + " except KeyError:\n", + " suffix_tok_id, prefix_tok_id, middle_tok_id, pad_tok_id = None, None, None, None\n", + " return suffix_tok_id, prefix_tok_id, middle_tok_id, pad_tok_id\n", + "\n", + "\n", + "## Adapted from https://github.com/bigcode-project/Megatron-LM/blob/6c4bf908df8fd86b4977f54bf5b8bd4b521003d1/megatron/data/gpt_dataset.py\n", + "def permute(\n", + " sample,\n", + " np_rng,\n", + " suffix_tok_id,\n", + " prefix_tok_id,\n", + " middle_tok_id,\n", + " pad_tok_id,\n", + " fim_rate=0.5,\n", + " fim_spm_rate=0.5,\n", + " truncate_or_pad=False,\n", + "):\n", + " \"\"\"\n", + " Take in a sample (list of tokens) and perform a FIM transformation on it with a probability of fim_rate, using two FIM modes:\n", + " PSM and SPM (with a probability of fim_spm_rate).\n", + " \"\"\"\n", + "\n", + " # The if condition will trigger with the probability of fim_rate\n", + " # This means FIM transformations will apply to samples with a probability of fim_rate\n", + " if np_rng.binomial(1, fim_rate):\n", + "\n", + " # Split the sample into prefix, middle, and suffix, based on randomly generated indices stored in the boundaries list.\n", + " boundaries = list(np_rng.randint(low=0, high=len(sample) + 1, size=2))\n", + " boundaries.sort()\n", + "\n", + " prefix = np.array(sample[: boundaries[0]], dtype=np.int64)\n", + " middle = np.array(sample[boundaries[0] : boundaries[1]], dtype=np.int64)\n", + " suffix = np.array(sample[boundaries[1] :], dtype=np.int64)\n", + "\n", + " if truncate_or_pad:\n", + " # calculate the new total length of the sample, taking into account tokens indicating prefix, middle, and suffix\n", + " new_length = suffix.shape[0] + prefix.shape[0] + middle.shape[0] + 3\n", + " diff = new_length - len(sample)\n", + "\n", + " # trancate or pad if there's a difference in length between the new length and the original\n", + " if diff > 0:\n", + " if suffix.shape[0] <= diff:\n", + " return sample, np_rng\n", + " suffix = suffix[: suffix.shape[0] - diff]\n", + " elif diff < 0:\n", + " suffix = np.concatenate([suffix, np.full((-1 * diff), pad_tok_id)])\n", + "\n", + " # With the probability of fim_spm_rateapply SPM variant of FIM transformations\n", + " # SPM: suffix, prefix, middle\n", + " if np_rng.binomial(1, fim_spm_rate):\n", + " new_sample = np.concatenate(\n", + " [\n", + " [prefix_tok_id, suffix_tok_id],\n", + " suffix,\n", + " [middle_tok_id],\n", + " prefix,\n", + " middle,\n", + " ]\n", + " )\n", + " # Otherwise, apply the PSM variant of FIM transformations\n", + " # PSM: prefix, suffix, middle\n", + " else:\n", + "\n", + " new_sample = np.concatenate(\n", + " [\n", + " [prefix_tok_id],\n", + " prefix,\n", + " [suffix_tok_id],\n", + " suffix,\n", + " [middle_tok_id],\n", + " middle,\n", + " ]\n", + " )\n", + " else:\n", + " # don't apply FIM transformations\n", + " new_sample = sample\n", + "\n", + " return list(new_sample), np_rng\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "AwW5FviD9xBH" + }, + "source": [ + "بیایید ConstantLengthDataset را تعریف کنیم، یک دیتاست قابل تکرار که قطعاتی با طول ثابت از توکنها را برمیگرداند. برای انجام این کار، بافر متنی را از مجموعه داده اصلی میخوانیم تا به محدودیتهای اندازه برسیم و سپس از توکنایزر برای تبدیل متن خام به ورودیهای توکنیزه شده استفاده میکنیم. به صورت اختیاری، ما تبدیلات FIM را روی برخی از توالیها انجام میدهیم (نسبت توالیهای تحت تاثیر پارامتر fim_rate کنترل میشود).\n", + "\n", + "پس از تعریف، میتوانیم نمونههایی از ConstantLengthDataset را هم از دادههای آموزشی و هم از دادههای اعتبارسنجی ایجاد کنیم." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "AgDW-692wzOl" + }, + "outputs": [], + "source": [ + "from torch.utils.data import IterableDataset\n", + "from torch.utils.data.dataloader import DataLoader\n", + "import random\n", + "\n", + "# Create an Iterable dataset that returns constant-length chunks of tokens from a stream of text files.\n", + "\n", + "class ConstantLengthDataset(IterableDataset):\n", + " \"\"\"\n", + " Iterable dataset that returns constant length chunks of tokens from stream of text files.\n", + " Args:\n", + " tokenizer (Tokenizer): The processor used for proccessing the data.\n", + " dataset (dataset.Dataset): Dataset with text files.\n", + " infinite (bool): If True the iterator is reset after dataset reaches end else stops.\n", + " seq_length (int): Length of token sequences to return.\n", + " num_of_sequences (int): Number of token sequences to keep in buffer.\n", + " chars_per_token (int): Number of characters per token used to estimate number of tokens in text buffer.\n", + " fim_rate (float): Rate (0.0 to 1.0) that sample will be permuted with FIM.\n", + " fim_spm_rate (float): Rate (0.0 to 1.0) of FIM permuations that will use SPM.\n", + " seed (int): Seed for random number generator.\n", + " \"\"\"\n", + "\n", + " def __init__(\n", + " self,\n", + " tokenizer,\n", + " dataset,\n", + " infinite=False,\n", + " seq_length=1024,\n", + " num_of_sequences=1024,\n", + " chars_per_token=3.6,\n", + " content_field=\"content\",\n", + " fim_rate=0.5,\n", + " fim_spm_rate=0.5,\n", + " seed=0,\n", + " ):\n", + " self.tokenizer = tokenizer\n", + " self.concat_token_id = tokenizer.eos_token_id\n", + " self.dataset = dataset\n", + " self.seq_length = seq_length\n", + " self.infinite = infinite\n", + " self.current_size = 0\n", + " self.max_buffer_size = seq_length * chars_per_token * num_of_sequences\n", + " self.content_field = content_field\n", + " self.fim_rate = fim_rate\n", + " self.fim_spm_rate = fim_spm_rate\n", + " self.seed = seed\n", + "\n", + " (\n", + " self.suffix_tok_id,\n", + " self.prefix_tok_id,\n", + " self.middle_tok_id,\n", + " self.pad_tok_id,\n", + " ) = get_fim_token_ids(self.tokenizer)\n", + " if not self.suffix_tok_id and self.fim_rate > 0:\n", + " print(\"FIM is not supported by tokenizer, disabling FIM\")\n", + " self.fim_rate = 0\n", + "\n", + " def __iter__(self):\n", + " iterator = iter(self.dataset)\n", + " more_examples = True\n", + " np_rng = np.random.RandomState(seed=self.seed)\n", + " while more_examples:\n", + " buffer, buffer_len = [], 0\n", + " while True:\n", + " if buffer_len >= self.max_buffer_size:\n", + " break\n", + " try:\n", + " buffer.append(next(iterator)[self.content_field])\n", + " buffer_len += len(buffer[-1])\n", + " except StopIteration:\n", + " if self.infinite:\n", + " iterator = iter(self.dataset)\n", + " else:\n", + " more_examples = False\n", + " break\n", + " tokenized_inputs = self.tokenizer(buffer, truncation=False)[\"input_ids\"]\n", + " all_token_ids = []\n", + "\n", + " for tokenized_input in tokenized_inputs:\n", + " # optionally do FIM permutations\n", + " if self.fim_rate > 0:\n", + " tokenized_input, np_rng = permute(\n", + " tokenized_input,\n", + " np_rng,\n", + " self.suffix_tok_id,\n", + " self.prefix_tok_id,\n", + " self.middle_tok_id,\n", + " self.pad_tok_id,\n", + " fim_rate=self.fim_rate,\n", + " fim_spm_rate=self.fim_spm_rate,\n", + " truncate_or_pad=False,\n", + " )\n", + "\n", + " all_token_ids.extend(tokenized_input + [self.concat_token_id])\n", + " examples = []\n", + " for i in range(0, len(all_token_ids), self.seq_length):\n", + " input_ids = all_token_ids[i : i + self.seq_length]\n", + " if len(input_ids) == self.seq_length:\n", + " examples.append(input_ids)\n", + " random.shuffle(examples)\n", + " for example in examples:\n", + " self.current_size += 1\n", + " yield {\n", + " \"input_ids\": torch.LongTensor(example),\n", + " \"labels\": torch.LongTensor(example),\n", + " }\n", + "\n", + "\n", + "train_dataset = ConstantLengthDataset(\n", + " tokenizer,\n", + " train_data,\n", + " infinite=True,\n", + " seq_length=SEQ_LENGTH,\n", + " chars_per_token=chars_per_token,\n", + " content_field=DATA_COLUMN,\n", + " fim_rate=FIM_RATE,\n", + " fim_spm_rate=FIM_SPM_RATE,\n", + " seed=SEED,\n", + ")\n", + "eval_dataset = ConstantLengthDataset(\n", + " tokenizer,\n", + " valid_data,\n", + " infinite=False,\n", + " seq_length=SEQ_LENGTH,\n", + " chars_per_token=chars_per_token,\n", + " content_field=DATA_COLUMN,\n", + " fim_rate=FIM_RATE,\n", + " fim_spm_rate=FIM_SPM_RATE,\n", + " seed=SEED,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "rxev1sk6tRW9" + }, + "source": [ + "## آماده سازی مدل" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "UCtWV-U42Eq_" + }, + "source": [ + ".حالا که دادهها آماده شدند، زمان بارگذاری مدل است! ما قصد داریم نسخه کوانتیده شدهی مدل را بارگذاری کنیم\n", + "\n", + "کوانتیزهسازی باعث کاهش استفاده از حافظه میشود، زیرا دادهها را با بیتهای کمتری نمایش میدهد. برای کوانتیزهسازی مدل از کتابخانهی bitsandbytes استفاده خواهیم کرد، زیرا این کتابخانه با transformer ها یکپارچگی خوبی دارد. تنها کاری که باید انجام دهیم پیکربندی اولیه bitsandbytes است و سپس میتوانیم هنگام بارگذاری مدل از آن استفاده کنیم.\n", + "\n", + "انواع مختلفی از کوانتیزهسازی ۴ بیتی وجود دارد، اما به طور کلی، ما برای عملکرد بهتر استفاده از کوانتیزهسازی NF4 را توصیه میکنیم (`bnb_4bit_quant_type=\"nf4\"`).\n", + "\n", + "گزینهی `bnb_4bit_use_double_quant` یک کوانتیزهسازی دوم را پس از کوانتیزهسازی اول اضافه میکند تا ۰.۴ بیت اضافی به ازای هر پارامتر ذخیره شود.\n", + "\n", + "برای اطلاعات بیشتر در مورد کوانتیزهسازی، به پست وبلاگ [\"Making LLMs even more accessible with bitsandbytes, 4-bit quantization and QLoRA\"](https://huggingface.co/blog/4bit-transformers-bitsandbytes) مراجعه کنید.\n", + "\n", + "پس از تعریف پیکربندی، آن را به متد from_pretrained ارسال کنید تا نسخه کوانتیده شدهی مدل بارگذاری شود." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "XuwoX6U2DUvK" + }, + "outputs": [], + "source": [ + "from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training\n", + "from peft.tuners.lora import LoraLayer\n", + "\n", + "load_in_8bit = False\n", + "\n", + "# 4-bit quantization\n", + "compute_dtype = getattr(torch, BNB_4BIT_COMPUTE_DTYPE)\n", + "\n", + "bnb_config = BitsAndBytesConfig(\n", + " load_in_4bit=True,\n", + " bnb_4bit_quant_type=\"nf4\",\n", + " bnb_4bit_compute_dtype=compute_dtype,\n", + " bnb_4bit_use_double_quant=USE_NESTED_QUANT,\n", + ")\n", + "\n", + "device_map = {\"\": 0}\n", + "\n", + "model = AutoModelForCausalLM.from_pretrained(\n", + " MODEL,\n", + " load_in_8bit=load_in_8bit,\n", + " quantization_config=bnb_config,\n", + " device_map=device_map,\n", + " use_cache=False, # We will be using gradient checkpointing\n", + " trust_remote_code=True,\n", + " use_flash_attention_2=True,\n", + ")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "bO9e2FV8D8ZF" + }, + "source": [ + "برای استفاده از یک مدل کوانتیده شده جهت آموزش، لازم است تابع `prepare_model_for_kbit_training` را فراخوانی کنید تا مدل کوانتیده شده را برای آموزش آمادهسازی کند." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "Qb_eB4xzEDBk" + }, + "outputs": [], + "source": [ + "model = prepare_model_for_kbit_training(model)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "lmnLjPZpDVtg" + }, + "source": [ + "حالا که مدل کمی سازی شده آماده است، می توانیم پیکربندی LoRA را تنظیم کنیم. LoRA با کاهش چشمگیر تعداد پارامترهای قابل آموزش، فرآیند تنظیم دقیق را کارآمدتر می کند.\n", + "\n", + "برای آموزش یک مدل با استفاده از تکنیک LoRA، باید مدل پایه را به عنوان یک `PeftModel` بپوشانیم. این شامل تعریف پیکربندی LoRA با `LoraConfig` و پوشاندن مدل اصلی با `get_peft_model()` با استفاده از `LoraConfig` است.\n", + "\n", + "برای کسب اطلاعات بیشتر در مورد LoRA و پارامترهای آن، به مستندات [PEFT documentation](https://huggingface.co/docs/peft/conceptual_guides/lora) مراجعه کنید." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "_pAUU2FR2Gey", + "outputId": "63328c2b-e693-49b1-ce0a-3ca8722f852a" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "trainable params: 5,554,176 || all params: 1,142,761,472 || trainable%: 0.4860310866343243\n" + ] + } + ], + "source": [ + "# Set up lora\n", + "peft_config = LoraConfig(\n", + " lora_alpha=LORA_ALPHA,\n", + " lora_dropout=LORA_DROPOUT,\n", + " r=LORA_R,\n", + " bias=\"none\",\n", + " task_type=\"CAUSAL_LM\",\n", + " target_modules=LORA_TARGET_MODULES.split(\",\"),\n", + ")\n", + "\n", + "model = get_peft_model(model, peft_config)\n", + "model.print_trainable_parameters()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "tHe7AElXzXVV" + }, + "source": [ + "همانطور که مشاهده می کنید، با به کارگیری تکنیک LoRA، اکنون نیاز به آموزش کمتر از ۱ درصد از پارامترها خواهیم داشت." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "T_CqVydc40IM" + }, + "source": [ + "## آموزش مدل" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Q_iN2khjrbD3" + }, + "source": [ + "حالا که داده ها را آماده کرده ایم و مدل را بهینه کرده ایم، آماده ایم تا همه چیز را کنار هم آورده و آموزش مدل را شروع کنیم.\n", + "\n", + "برای ایجاد یک `Trainer`، باید پیکربندی آموزش را تعریف کنید. مهمترین بخش `TrainingArguments` است، کلاسی که شامل تمام ویژگی های پیکربندی آموزش می شود.\n", + "\n", + "این موارد مشابه هر نوع فرآیند آموزش مدل دیگری هستند که قبلا اجرا کردهاید، بنابراین در اینجا به جزئیات نمیپردازیم." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "65QHS8l1tKQe" + }, + "outputs": [], + "source": [ + "train_data.start_iteration = 0\n", + "\n", + "\n", + "training_args = TrainingArguments(\n", + " output_dir=f\"Your_HF_username/{OUTPUT_DIR}\",\n", + " dataloader_drop_last=True,\n", + " evaluation_strategy=\"steps\",\n", + " save_strategy=\"steps\",\n", + " max_steps=MAX_STEPS,\n", + " eval_steps=EVAL_FREQ,\n", + " save_steps=SAVE_FREQ,\n", + " logging_steps=LOG_FREQ,\n", + " per_device_train_batch_size=BATCH_SIZE,\n", + " per_device_eval_batch_size=BATCH_SIZE,\n", + " learning_rate=LR,\n", + " lr_scheduler_type=LR_SCHEDULER_TYPE,\n", + " warmup_steps=NUM_WARMUP_STEPS,\n", + " gradient_accumulation_steps=GR_ACC_STEPS,\n", + " gradient_checkpointing=True,\n", + " fp16=FP16,\n", + " bf16=BF16,\n", + " weight_decay=WEIGHT_DECAY,\n", + " push_to_hub=True,\n", + " include_tokens_per_second=True,\n", + ")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "kB_fLRex09ut" + }, + "source": [ + "به عنوان آخرین مرحله، `Trainer` را ایجاد کرده و متد `train` را فراخوانی کنید." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 1000 + }, + "id": "rS3nVwhUC69O", + "outputId": "61a5bdb2-b7d0-4aed-8290-4bf20c2ccd38" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Training...\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "
Step | \n", + "Training Loss | \n", + "Validation Loss | \n", + "
---|---|---|
100 | \n", + "5.524600 | \n", + "7.456872 | \n", + "
200 | \n", + "5.617800 | \n", + "7.262190 | \n", + "
300 | \n", + "5.129100 | \n", + "6.410039 | \n", + "
400 | \n", + "5.052200 | \n", + "6.306774 | \n", + "
500 | \n", + "5.202900 | \n", + "6.117062 | \n", + "
600 | \n", + "4.654100 | \n", + "6.018349 | \n", + "
700 | \n", + "5.100200 | \n", + "6.000355 | \n", + "
800 | \n", + "5.049800 | \n", + "5.889457 | \n", + "
900 | \n", + "4.541200 | \n", + "5.813823 | \n", + "
1000 | \n", + "5.000700 | \n", + "5.834208 | \n", + "
1100 | \n", + "5.026500 | \n", + "5.781939 | \n", + "
1200 | \n", + "4.411800 | \n", + "5.720596 | \n", + "
1300 | \n", + "4.782500 | \n", + "5.736376 | \n", + "
1400 | \n", + "4.980200 | \n", + "5.712276 | \n", + "
1500 | \n", + "4.368700 | \n", + "5.689637 | \n", + "
1600 | \n", + "4.884700 | \n", + "5.675920 | \n", + "
1700 | \n", + "4.914400 | \n", + "5.662421 | \n", + "
1800 | \n", + "4.248700 | \n", + "5.660122 | \n", + "
1900 | \n", + "4.798400 | \n", + "5.664026 | \n", + "
2000 | \n", + "4.704200 | \n", + "5.655665 | \n", + "
"
+ ],
+ "text/plain": [
+ "