#
تنظیم دقیق مدل زبانی بزرگ مبتنی بر کد با کد‌های شخصی توسط یک هسته GPU

نویسنده: Maria Khalusova

مدل‌های زبانی بزرگ‌ ‫(LLM) عمومیِ مبتنی بر کد از جمله Codex ،StarCoder و CodeLlama در تولید کدهایی که از اصول کلی برنامه ‌نویسی و سینتکس برنامه‌نویسی پیروی می کنند عالی هستند، اما ممکن است با قراردادهای داخلی سازمان مطابقت نداشته باشند یا از کتابخانه های اختصاصی مطلع نباشند.

در این دفترچه، نحوه تنظیم دقیق ‫(fine-tune) یک LLM مبتنی بر کد، بر روی کد‌های خصوصی ‌را برای بهبود فهم بافتاری آن در متن و افزایش کاربرد مدل برای نیازهای به‌ خصوص سازمان شما نشان خواهیم داد. از آنجایی که LLM های مبتنی بر کد بسیار بزرگ هستند، تنظیم دقیق آنها به روش سنتی می‌تواند از نظر منابع پر‌مصرف باشد. نگران نباشید! ما نحوه بهینه سازی تنظیم دقیق را بر روی یک هسته GPU نشان خواهیم داد.

##
دیتاست

برای این مثال، ما ۱۰ ریپازیتوری عمومی برتر ‫Hugging Face را از Github انتخاب کردیم. ما فایل‌های غیر کد مانند تصاویر، فایل‌های صوتی، ارائه‌ها و غیره را از داده‌ها حذف کرده‌ایم. برای Jupyter Notebook ها، ما فقط سلول‌هایی را که حاوی کد هستند نگه داشته‌ایم. کد حاصل به عنوان دیتاستی ذخیره می‌شود که می‌توانید آن را در Hugging Face Hub تحت `smangrul/hf-stack-v1` پیدا کنید. این دیتاست شامل شناسه ریپازیتوری، مسیر فایل و محتوای فایل است.

##
مدل

ما مدل ‫`bigcode/starcoderbase-1b` که یک مدل با یک میلیارد پارامتر است و روی بیش از ۸۰ زبان برنامه نویسی آموزش دیده است را تنظیم دقیق می‌کنیم. دسترسی به این مدل محدود است، بنابرین اگر قصد دارید این دفترچه را با همین مدل اجرا کنید، باید از صفحه مدل به آن دسترسی پیدا کنید. برای انجام این کار، با حساب کاربری Hugging Face خود وارد شوید.

    from huggingface_hub import notebook_login

    notebook_login()

برای شروع، بیایید تمام کتابخانه‌های ضروری را نصب کنیم. همانطور که می‌بینید، علاوه بر `transformers` و `datasets`، از `peft`، `bitsandbytes` و `flash-attn` برای بهینه‌سازی آموزش مدل استفاده خواهیم کرد.

با استفاده از تکنیک‌های آموزش کارآمد پارامتر، می‌توانیم این دفترچه را روی یک GPU A100 با رم بالا اجرا کنیم.

    !pip install -q transformers datasets peft bitsandbytes flash-attn

اکنون چند متغیر تعریف می‌کنیم. در بازی کردن با این متغیر‌ها و تعویض مقدار آنها راحت باشید.

    MODEL = "bigcode/starcoderbase-1b"  # Model checkpoint on the Hugging Face Hub
    DATASET = "smangrul/hf-stack-v1"  # Dataset on the Hugging Face Hub
    DATA_COLUMN = "content"  # Column name containing the code content

    SEQ_LENGTH = 2048  # Sequence length

    # Training arguments
    MAX_STEPS = 2000  # max_steps
    BATCH_SIZE = 16  # batch_size
    GR_ACC_STEPS = 1  # gradient_accumulation_steps
    LR = 5e-4  # learning_rate
    LR_SCHEDULER_TYPE = "cosine"  # lr_scheduler_type
    WEIGHT_DECAY = 0.01  # weight_decay
    NUM_WARMUP_STEPS = 30  # num_warmup_steps
    EVAL_FREQ = 100  # eval_freq
    SAVE_FREQ = 100  # save_freq
    LOG_FREQ = 25  # log_freq
    OUTPUT_DIR = "peft-starcoder-lora-a100"  # output_dir
    BF16 = True  # bf16
    FP16 = False  # no_fp16

    # FIM trasformations arguments
    FIM_RATE = 0.5  # fim_rate
    FIM_SPM_RATE = 0.5  # fim_spm_rate

    # LORA
    LORA_R = 8  # lora_r
    LORA_ALPHA = 32  # lora_alpha
    LORA_DROPOUT = 0.0  # lora_dropout
    LORA_TARGET_MODULES = "c_proj,c_attn,q_attn,c_fc,c_proj"  # lora_target_modules

    # bitsandbytes config
    USE_NESTED_QUANT = True  # use_nested_quant
    BNB_4BIT_COMPUTE_DTYPE = "bfloat16"  # bnb_4bit_compute_dtype

    SEED = 0

    from transformers import (
        AutoModelForCausalLM,
        AutoTokenizer,
        Trainer,
        TrainingArguments,
        logging,
        set_seed,
        BitsAndBytesConfig,
    )

    set_seed(SEED)

##
آماده‌سازی داده‌ها

با بارگذاری داده شروع کنید. از آنجایی که به احتمال زیاد دیتاست بسیار بزرگ است، حتما حالت استریم ‫(streaming) را فعال کنید. استریم به ما این امکان را می‌دهد که داده‌ها را به تدریج در حالی که روی دیتاست تکرار می‌کنیم، بارگذاری کنیم به جای اینکه کل دیتاست را به طور یکجا دانلود کنیم.

ما ۴۰۰۰ نمونه اول را به عنوان دیتاست اعتبارسنجی ‫(validation set) اختصاص خواهیم داد و باقی‌مانده به عنوان داده‌های آموزشی در نظر گرفته می‌شود.

    from datasets import load_dataset
    import torch
    from tqdm import tqdm

    dataset = load_dataset(
        DATASET,
        data_dir="data",
        split="train",
        streaming=True,
    )

    valid_data = dataset.take(4000)
    train_data = dataset.skip(4000)
    train_data = train_data.shuffle(buffer_size=5000, seed=SEED)

تبدیلات اختیاری ‫FIM (اختیاری - مدل پرکردن میانی)

مدل‌های زبان خودبازگشت ‫(Autoregressive) معمولا توالی‌ها را از چپ به راست تولید می‌کنند. با اعمال تبدیلات FIM، مدل همچنین می‌تواند یاد بگیرد که متن را در بخش‌های میانی پر کند. برای اطلاعات بیشتر در مورد این تکنیک، به مقاله‌ی Efficient Training of Language Models to Fill in the Middle مراجعه کنید.

ما در اینجا تبدیلات ‫FIM را تعریف می‌کنیم و هنگام ایجاد دیتاست قابل تکرار (Iterable Dataset) از آن‌ها استفاده خواهیم کرد. با این حال، اگر می‌خواهید تبدیلی صورت نگیرد، می‌توانید پارامتر fim_rate را روی ۰ تنظیم کنید.

    >>> tokenizer = AutoTokenizer.from_pretrained(MODEL, trust_remote_code=True)

    >>> def chars_token_ratio(dataset, tokenizer, data_column, nb_examples=400):
    ...     """
    ...     Estimate the average number of characters per token in the dataset.
    ...     """

    ...     total_characters, total_tokens = 0, 0
    ...     for _, example in tqdm(zip(range(nb_examples), iter(dataset)), total=nb_examples):
    ...         total_characters += len(example[data_column])
    ...         total_tokens += len(tokenizer(example[data_column]).tokens())

    ...     return total_characters / total_tokens

    >>> chars_per_token = chars_token_ratio(train_data, tokenizer, DATA_COLUMN)
    >>> print(f"The character to token ratio of the dataset is: {chars_per_token:.2f}")

    The character to token ratio of the dataset is: 2.43

نسبت کاراکتر به توکن همچنین می‌تواند به عنوان شاخصی از کیفیت توکنیزه‌کردن متن استفاده شود. برای مثال، نسبت کاراکتر به توکن ۱٫۰ به این معنی است که هر کاراکتر با یک توکن نمایش داده می‌شود، که چندان معنی‌دار نیست. این نشان‌دهنده توکنیزه‌کردن ضعیف است. در متن استاندارد انگلیسی، یک توکن معمولاً معادل تقریباً چهار کاراکتر است، یعنی نسبت کاراکتر به توکن حدود ۴٫۰ است. در دیتاست کد انتظار داریم این نسبت کمتر باشد، اما به طور کلی، عددی بین ۲٫۰ تا ۳٫۵ را می توان به اندازه کافی خوب در نظر گرفت.

تبدیلات ‫FIM (اختیاری)

مدل‌های زبان خودبازگشت ‫(Autoregressive) معمولا توالی‌ها را از چپ به راست تولید می‌کنند. با اعمال تبدیلات FIM، مدل همچنین می‌تواند یاد بگیرد که متن را در بخش‌های میانی پر کند. برای اطلاعات بیشتر در مورد این تکنیک، به مقاله‌ی ” آموزش کارآمد مدل‌های زبان برای پر کردن بخش میان “مراجعه کنید.

ما در اینجا تبدیلات ‫FIM را تعریف می‌کنیم و هنگام ایجاد دیتاست قابل تکرار (Iterable Dataset) از آن‌ها استفاده خواهیم کرد. با این حال، اگر می‌خواهید تبدیلی صورت نگیرد، می‌توانید پارامتر fim_rate را روی ۰ تنظیم کنید.

    import functools
    import numpy as np

    # Helper function to get token ids of the special tokens for prefix, suffix and middle for FIM transformations.
    @functools.lru_cache(maxsize=None)
    def get_fim_token_ids(tokenizer):
        try:
            FIM_PREFIX, FIM_MIDDLE, FIM_SUFFIX, FIM_PAD = tokenizer.special_tokens_map["additional_special_tokens"][1:5]
            suffix_tok_id, prefix_tok_id, middle_tok_id, pad_tok_id = (
                tokenizer.vocab[tok] for tok in [FIM_SUFFIX, FIM_PREFIX, FIM_MIDDLE, FIM_PAD]
            )
        except KeyError:
            suffix_tok_id, prefix_tok_id, middle_tok_id, pad_tok_id = None, None, None, None
        return suffix_tok_id, prefix_tok_id, middle_tok_id, pad_tok_id

    ## Adapted from https://github.com/bigcode-project/Megatron-LM/blob/6c4bf908df8fd86b4977f54bf5b8bd4b521003d1/megatron/data/gpt_dataset.py
    def permute(
        sample,
        np_rng,
        suffix_tok_id,
        prefix_tok_id,
        middle_tok_id,
        pad_tok_id,
        fim_rate=0.5,
        fim_spm_rate=0.5,
        truncate_or_pad=False,
    ):
        """
        Take in a sample (list of tokens) and perform a FIM transformation on it with a probability of fim_rate, using two FIM modes:
        PSM and SPM (with a probability of fim_spm_rate).
        """

        # The if condition will trigger with the probability of fim_rate
        # This means FIM transformations will apply to samples with a probability of fim_rate
        if np_rng.binomial(1, fim_rate):

            # Split the sample into prefix, middle, and suffix, based on randomly generated indices stored in the boundaries list.
            boundaries = list(np_rng.randint(low=0, high=len(sample) + 1, size=2))
            boundaries.sort()

            prefix = np.array(sample[: boundaries[0]], dtype=np.int64)
            middle = np.array(sample[boundaries[0] : boundaries[1]], dtype=np.int64)
            suffix = np.array(sample[boundaries[1] :], dtype=np.int64)

            if truncate_or_pad:
                # calculate the new total length of the sample, taking into account tokens indicating prefix, middle, and suffix
                new_length = suffix.shape[0] + prefix.shape[0] + middle.shape[0] + 3
                diff = new_length - len(sample)

                # trancate or pad if there's a difference in length between the new length and the original
                if diff > 0:
                    if suffix.shape[0] <= diff:
                        return sample, np_rng
                    suffix = suffix[: suffix.shape[0] - diff]
                elif diff < 0:
                    suffix = np.concatenate([suffix, np.full((-1 * diff), pad_tok_id)])

            # With the probability of fim_spm_rateapply SPM variant of FIM transformations
            # SPM: suffix, prefix, middle
            if np_rng.binomial(1, fim_spm_rate):
                new_sample = np.concatenate(
                    [
                        [prefix_tok_id, suffix_tok_id],
                        suffix,
                        [middle_tok_id],
                        prefix,
                        middle,
                    ]
                )
            # Otherwise, apply the PSM variant of FIM transformations
            # PSM: prefix, suffix, middle
            else:

                new_sample = np.concatenate(
                    [
                        [prefix_tok_id],
                        prefix,
                        [suffix_tok_id],
                        suffix,
                        [middle_tok_id],
                        middle,
                    ]
                )
        else:
            # don't apply FIM transformations
            new_sample = sample

        return list(new_sample), np_rng

بیایید ‫ConstantLengthDataset را تعریف کنیم، یک دیتاست قابل تکرار که قطعاتی با طول ثابت از توکن‌ها را برمی‌گرداند. برای انجام این کار، بافر متنی را از مجموعه داده اصلی می‌خوانیم تا به محدودیت‌های اندازه برسیم و سپس از توکنایزر برای تبدیل متن خام به ورودی‌های توکنیزه شده استفاده می‌کنیم. به صورت اختیاری، ما تبدیلات FIM را روی برخی از توالی‌ها انجام می‌دهیم (نسبت توالی‌های تحت تاثیر پارامتر fim_rate کنترل می‌شود).

پس از تعریف، می‌توانیم نمونه‌هایی از ‫ConstantLengthDataset را هم از داده‌های آموزشی و هم از داده‌های اعتبارسنجی ایجاد کنیم.

    from torch.utils.data import IterableDataset
    from torch.utils.data.dataloader import DataLoader
    import random

    # Create an Iterable dataset that returns constant-length chunks of tokens from a stream of text files.

    class ConstantLengthDataset(IterableDataset):
        """
        Iterable dataset that returns constant length chunks of tokens from stream of text files.
            Args:
                tokenizer (Tokenizer): The processor used for proccessing the data.
                dataset (dataset.Dataset): Dataset with text files.
                infinite (bool): If True the iterator is reset after dataset reaches end else stops.
                seq_length (int): Length of token sequences to return.
                num_of_sequences (int): Number of token sequences to keep in buffer.
                chars_per_token (int): Number of characters per token used to estimate number of tokens in text buffer.
                fim_rate (float): Rate (0.0 to 1.0) that sample will be permuted with FIM.
                fim_spm_rate (float): Rate (0.0 to 1.0) of FIM permuations that will use SPM.
                seed (int): Seed for random number generator.
        """

        def __init__(
            self,
            tokenizer,
            dataset,
            infinite=False,
            seq_length=1024,
            num_of_sequences=1024,
            chars_per_token=3.6,
            content_field="content",
            fim_rate=0.5,
            fim_spm_rate=0.5,
            seed=0,
        ):
            self.tokenizer = tokenizer
            self.concat_token_id = tokenizer.eos_token_id
            self.dataset = dataset
            self.seq_length = seq_length
            self.infinite = infinite
            self.current_size = 0
            self.max_buffer_size = seq_length * chars_per_token * num_of_sequences
            self.content_field = content_field
            self.fim_rate = fim_rate
            self.fim_spm_rate = fim_spm_rate
            self.seed = seed

            (
                self.suffix_tok_id,
                self.prefix_tok_id,
                self.middle_tok_id,
                self.pad_tok_id,
            ) = get_fim_token_ids(self.tokenizer)
            if not self.suffix_tok_id and self.fim_rate > 0:
                print("FIM is not supported by tokenizer, disabling FIM")
                self.fim_rate = 0

        def __iter__(self):
            iterator = iter(self.dataset)
            more_examples = True
            np_rng = np.random.RandomState(seed=self.seed)
            while more_examples:
                buffer, buffer_len = [], 0
                while True:
                    if buffer_len >= self.max_buffer_size:
                        break
                    try:
                        buffer.append(next(iterator)[self.content_field])
                        buffer_len += len(buffer[-1])
                    except StopIteration:
                        if self.infinite:
                            iterator = iter(self.dataset)
                        else:
                            more_examples = False
                            break
                tokenized_inputs = self.tokenizer(buffer, truncation=False)["input_ids"]
                all_token_ids = []

                for tokenized_input in tokenized_inputs:
                    # optionally do FIM permutations
                    if self.fim_rate > 0:
                        tokenized_input, np_rng = permute(
                            tokenized_input,
                            np_rng,
                            self.suffix_tok_id,
                            self.prefix_tok_id,
                            self.middle_tok_id,
                            self.pad_tok_id,
                            fim_rate=self.fim_rate,
                            fim_spm_rate=self.fim_spm_rate,
                            truncate_or_pad=False,
                        )

                    all_token_ids.extend(tokenized_input + [self.concat_token_id])
                examples = []
                for i in range(0, len(all_token_ids), self.seq_length):
                    input_ids = all_token_ids[i : i + self.seq_length]
                    if len(input_ids) == self.seq_length:
                        examples.append(input_ids)
                random.shuffle(examples)
                for example in examples:
                    self.current_size += 1
                    yield {
                        "input_ids": torch.LongTensor(example),
                        "labels": torch.LongTensor(example),
                    }

    train_dataset = ConstantLengthDataset(
        tokenizer,
        train_data,
        infinite=True,
        seq_length=SEQ_LENGTH,
        chars_per_token=chars_per_token,
        content_field=DATA_COLUMN,
        fim_rate=FIM_RATE,
        fim_spm_rate=FIM_SPM_RATE,
        seed=SEED,
    )
    eval_dataset = ConstantLengthDataset(
        tokenizer,
        valid_data,
        infinite=False,
        seq_length=SEQ_LENGTH,
        chars_per_token=chars_per_token,
        content_field=DATA_COLUMN,
        fim_rate=FIM_RATE,
        fim_spm_rate=FIM_SPM_RATE,
        seed=SEED,
    )

##
آماده‌ سازی مدل

.حالا که داده‌ها آماده شدند، زمان بارگذاری مدل است! ما قصد داریم نسخه کوانتیده شده‌ی مدل را بارگذاری کنیم

کوانتیز‌ه‌سازی باعث کاهش استفاده از حافظه می‌شود، زیرا داده‌ها را با بیت‌های کمتری نمایش می‌دهد. برای کوانتیزه‌سازی مدل از کتابخانه‌ی ‫bitsandbytes استفاده خواهیم کرد، زیرا این کتابخانه با transformer ها یکپارچگی خوبی دارد. تنها کاری که باید انجام دهیم پیکربندی اولیه bitsandbytes است و سپس می‌توانیم هنگام بارگذاری مدل از آن استفاده کنیم.

انواع مختلفی از کوانتیزه‌سازی ۴ بیتی وجود دارد، اما به طور کلی، ما برای عملکرد بهتر استفاده از کوانتیزه‌سازی ‫NF4 را توصیه می‌کنیم (`bnb_4bit_quant_type="nf4"`).

گزینه‌ی `bnb_4bit_use_double_quant` یک کوانتیزه‌سازی دوم را پس از کوانتیزه‌سازی اول اضافه می‌کند تا ۰.۴ بیت اضافی به ازای هر پارامتر ذخیره شود.

برای اطلاعات بیشتر در مورد کوانتیزه‌سازی، به پست وبلاگ “Making LLMs even more accessible with bitsandbytes, 4-bit quantization and QLoRA” مراجعه کنید.

پس از تعریف پیکربندی، آن را به متد ‫from_pretrained ارسال کنید تا نسخه کوانتیده شده‌ی مدل بارگذاری شود.

    from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
    from peft.tuners.lora import LoraLayer

    load_in_8bit = False

    # 4-bit quantization
    compute_dtype = getattr(torch, BNB_4BIT_COMPUTE_DTYPE)

    bnb_config = BitsAndBytesConfig(
        load_in_4bit=True,
        bnb_4bit_quant_type="nf4",
        bnb_4bit_compute_dtype=compute_dtype,
        bnb_4bit_use_double_quant=USE_NESTED_QUANT,
    )

    device_map = {"": 0}

    model = AutoModelForCausalLM.from_pretrained(
        MODEL,
        load_in_8bit=load_in_8bit,
        quantization_config=bnb_config,
        device_map=device_map,
        use_cache=False,  # We will be using gradient checkpointing
        trust_remote_code=True,
        use_flash_attention_2=True,
    )

برای استفاده از یک مدل کوانتیده شده جهت آموزش، لازم است تابع ‫`prepare_model_for_kbit_training` را فراخوانی کنید تا مدل کوانتیده شده را برای آموزش آماده‌سازی کند.

    model = prepare_model_for_kbit_training(model)

حالا که مدل کمی سازی شده آماده است، می توانیم پیکربندی ‫LoRA را تنظیم کنیم. LoRA با کاهش چشمگیر تعداد پارامترهای قابل آموزش، فرآیند تنظیم دقیق را کارآمدتر می کند.

برای آموزش یک مدل با استفاده از تکنیک ‫LoRA، باید مدل پایه را به عنوان یک `PeftModel` بپوشانیم. این شامل تعریف پیکربندی LoRA با `LoraConfig` و پوشاندن مدل اصلی با `get_peft_model()` با استفاده از `LoraConfig` است.

برای کسب اطلاعات بیشتر در مورد ‫LoRA و پارامترهای آن، به مستندات PEFT documentation مراجعه کنید.

    >>> # Set up lora
    >>> peft_config = LoraConfig(
    ...     lora_alpha=LORA_ALPHA,
    ...     lora_dropout=LORA_DROPOUT,
    ...     r=LORA_R,
    ...     bias="none",
    ...     task_type="CAUSAL_LM",
    ...     target_modules=LORA_TARGET_MODULES.split(","),
    ... )

    >>> model = get_peft_model(model, peft_config)
    >>> model.print_trainable_parameters()

    trainable params: 5,554,176 || all params: 1,142,761,472 || trainable%: 0.4860310866343243

همانطور که مشاهده می کنید، با به کارگیری تکنیک ‫LoRA، اکنون نیاز به آموزش کمتر از ۱ درصد از پارامترها خواهیم داشت.

##
آموزش مدل

حالا که داده ها را آماده کرده ایم و مدل را بهینه کرده ایم، آماده ایم تا همه چیز را کنار هم آورده و آموزش مدل را شروع کنیم.

برای ایجاد یک ‫`Trainer`، باید پیکربندی آموزش را تعریف کنید. مهمترین بخش `TrainingArguments` است، کلاسی که شامل تمام ویژگی های پیکربندی آموزش می شود.

این موارد مشابه هر نوع فرآیند آموزش مدل دیگری هستند که قبلا اجرا کرده‌اید،‫ بنابراین در اینجا به جزئیات نمی‌پردازیم.

    train_data.start_iteration = 0

    training_args = TrainingArguments(
        output_dir=f"Your_HF_username/{OUTPUT_DIR}",
        dataloader_drop_last=True,
        evaluation_strategy="steps",
        save_strategy="steps",
        max_steps=MAX_STEPS,
        eval_steps=EVAL_FREQ,
        save_steps=SAVE_FREQ,
        logging_steps=LOG_FREQ,
        per_device_train_batch_size=BATCH_SIZE,
        per_device_eval_batch_size=BATCH_SIZE,
        learning_rate=LR,
        lr_scheduler_type=LR_SCHEDULER_TYPE,
        warmup_steps=NUM_WARMUP_STEPS,
        gradient_accumulation_steps=GR_ACC_STEPS,
        gradient_checkpointing=True,
        fp16=FP16,
        bf16=BF16,
        weight_decay=WEIGHT_DECAY,
        push_to_hub=True,
        include_tokens_per_second=True,
    )

به عنوان آخرین مرحله، ‫`Trainer` را ایجاد کرده و متد ‫`train` را فراخوانی کنید.

    >>> trainer = Trainer(model=model, args=training_args, train_dataset=train_dataset, eval_dataset=eval_dataset)

    >>> print("Training...")
    >>> trainer.train()

    Training...

در نهایت، می‌توانید مدل تنظیم‌ دقیق شده را به ریپازیتوی هاب خود پوش کنید تا با تیم خود به اشتراک بگذارید.

    trainer.push_to_hub()

##
استنتاج ‫ (Inference)

بعد از اینکه مدل بر روی هاب آپلود شد، می‌توانیم برای استنتاج از آن استفاده کنیم. برای انجام این کار، ابتدا مدل پایه اصلی و توکنایزر آن را مقداردهی اولیه می‌کنیم. سپس، نیاز داریم تا وزن‌های تنظیم‌ دقیق شده را با مدل پایه ادغام ‫(merge) کنیم.

    from peft import PeftModel
    import torch

    # load the original model first
    tokenizer = AutoTokenizer.from_pretrained(MODEL, trust_remote_code=True)
    base_model = AutoModelForCausalLM.from_pretrained(
        MODEL,
        quantization_config=None,
        device_map=None,
        trust_remote_code=True,
        torch_dtype=torch.bfloat16,
    ).cuda()

    # merge fine-tuned weights with the base model
    peft_model_id = f"Your_HF_username/{OUTPUT_DIR}"
    model = PeftModel.from_pretrained(base_model, peft_model_id)
    model.merge_and_unload()

حالا می‌توانیم از مدل ادغام‌شده برای استنتاج استفاده کنیم. برای سهولت، ما یک تابع به نام ‫`get_code_completion` تعریف می‌کنیم - در تغییر پارامترهای تولید متن راحت باشید!

    def get_code_completion(prefix, suffix):
        text = prompt = f"""<fim_prefix>{prefix}<fim_suffix>{suffix}<fim_middle>"""
        model.eval()
        outputs = model.generate(
            input_ids=tokenizer(text, return_tensors="pt").input_ids.cuda(),
            max_new_tokens=128,
            temperature=0.2,
            top_k=50,
            top_p=0.95,
            do_sample=True,
            repetition_penalty=1.0,
        )
        return tokenizer.batch_decode(outputs, skip_special_tokens=True)[0]

حال برای دریافت ویژگی کد تکمیل‌ شونده، تنها کاری که باید انجام دهیم فراخوانی تابع ‫`get_code_complete` است. ورودی‌های این تابع چند خط اولی که می‌خواهید تکمیل شوند به عنوان پیشوند و یک رشته خالی به عنوان پسوند هستند.

    >>> prefix = """from peft import LoraConfig, TaskType, get_peft_model
    ... from transformers import AutoModelForCausalLM
    ... peft_config = LoraConfig(
    ... """
    >>> suffix = """"""

    ... print(get_code_completion(prefix, suffix))

    from peft import LoraConfig, TaskType, get_peft_model
    from transformers import AutoModelForCausalLM
    peft_config = LoraConfig(
        task_type=TaskType.CAUSAL_LM,
        r=8,
        lora_alpha=32,
        target_modules=["q_proj", "v_proj"],
        lora_dropout=0.1,
        bias="none",
        modules_to_save=["q_proj", "v_proj"],
        inference_mode=False,
    )
    model = AutoModelForCausalLM.from_pretrained("gpt2")
    model = get_peft_model(model, peft_config)
    model.print_trainable_parameters()

همانطور که مشاهده کردید، به عنوان شخصی که در این دفترچه از کتابخانه ‫PEFT استفاده کردید، نتیجه تولید شده برای ایجاد یک `LoraConfig` بسیار خوب است!

اگر به سلولی که مدل را برای استنتاج ایجاد کردیم برگردید و خطوطی را که وزن‌های تنظیم‌ دقیق شده را ادغام می‌کنند کامنت کنید، می‌توانید ببینید که مدل اصلی برای همان پیشوند دقیقاً چه چیزی تولید می‌کرد‫.

    >>> prefix = """from peft import LoraConfig, TaskType, get_peft_model
    ... from transformers import AutoModelForCausalLM
    ... peft_config = LoraConfig(
    ... """
    >>> suffix = """"""

    ... print(get_code_completion(prefix, suffix))

    from peft import LoraConfig, TaskType, get_peft_model
    from transformers import AutoModelForCausalLM
    peft_config = LoraConfig(
        model_name_or_path="facebook/wav2vec2-base-960h",
        num_labels=1,
        num_features=1,
        num_hidden_layers=1,
        num_attention_heads=1,
        num_hidden_layers_per_attention_head=1,
        num_attention_heads_per_hidden_layer=1,
        hidden_size=1024,
        hidden_dropout_prob=0.1,
        hidden_act="gelu",
        hidden_act_dropout_prob=0.1,
        hidden

در حالی که خروجی، سینتکس پایتون است، اما همانطور که می بینید، مدل اصلی هیچ درکی از کارکرد یک ‫LoraConfig ندارد.

برای اینکه چگونه عملکرد این نوع تنظیم دقیق را با تنظیم دقیق کامل مقایسه کنید و برای آموختن چگونگی استفاده از چنین مدلی به عنوان دستیار-شخصی در ‫ vscode از طریق Inference Endpoints یا به صورت محلی، به پست وبلاگ “Personal Copilot: Train Your Own Coding Assistant” مراجعه کنید.

این دفترچه، مکمل پست وبلاگ اصلی است.

< > Update on GitHub