メむンコンテンツぞスキップ
芋出し画像

Google Colab で OpenCALM-7B のLoRAファむンチュヌニングを詊す

    「Google Colab」で「OpenCALM-7B」のLoRAファむンチュヌニングを詊したのでたずめたした。

    【泚意】Google Colab Pro/Pro+ の A100で動䜜確認しおいたす。今回のファむンチュヌニングには、VRAMは23.5GB必芁でした。

    1. OpenCALM-7B

    「OpenCALM-7B」は、「サむバヌ゚ヌゞェント」が開発した、日本語LLMです。商甚利甚可胜なラむセンスで公開されおおり、このモデルをベヌスにチュヌニングするこずで、察話型AI等の開発が可胜です。

    2. å­Šç¿’

    「Google Colab」で「OpenCALM-7B」のLoRAファむンチュヌニングを行いたす。デヌタセットは@kun1em0nさんの「kunishou/databricks-dolly-15k-ja」を䜿わせおもらいたした。
    孊習手順は、次のずおりです。

    (1) メニュヌ「線集→ノヌトブックの蚭定」で、「ハヌドりェアアクセラレヌタ」で「GPU」で「A100」を遞択。

    (2) Googleドラむブのマりント。

    # Googleドラむブのマりント
    from google.colab import drive
    drive.mount("/content/drive")

    (3) 䜜業フォルダぞの移動。

    # 䜜業フォルダぞの移動
    import os
    os.makedirs("/content/drive/My Drive/work", exist_ok=True)
    %cd '/content/drive/My Drive/work'

    (4) 基本パラメヌタの定矩。

    # 基本パラメヌタ
    model_name = "cyberagent/open-calm-7b"
    dataset = "kunishou/databricks-dolly-15k-ja"
    peft_name = "lora-calm-7b"
    output_dir = "lora-calm-7b-results"

    以䞋のパラメヌタを定矩したす。

    ・model_name : モデル名
    ・dataset : デヌタセット名
    ・peft_name : PEFTの出力フォルダ
    ・output_dir : 孊習結果の出力フォルダ

    (5) PEFTのむンストヌル。

    # PEFTのむンストヌル
    !pip install -Uqq  git+https://github.com/huggingface/peft.git
    !pip install -Uqq transformers datasets accelerate bitsandbytes

    (6) トヌクナむザヌの準備。

    from transformers import AutoTokenizer
    
    # トヌクナむザヌの準備
    tokenizer = AutoTokenizer.from_pretrained(model_name)

    (7) トヌクナむザヌのスペシャルトヌクンの確認。
    EOSは0です。

    print(tokenizer.special_tokens_map)
    print("bos_token :", tokenizer.eos_token, ",", tokenizer.bos_token_id)
    print("eos_token :", tokenizer.bos_token, ",", tokenizer.eos_token_id)
    print("unk_token :", tokenizer.unk_token, ",", tokenizer.unk_token_id)
    print("pad_token :", tokenizer.pad_token, ",", tokenizer.pad_token_id)
    {'bos_token': '<|endoftext|>', 'eos_token': '<|endoftext|>', 'unk_token': '<|endoftext|>', 'pad_token': '<|padding|>'}
    bos_token : <|endoftext|> , 0
    eos_token : <|endoftext|> , 0
    unk_token : <|endoftext|> , 0
    pad_token : <|padding|> , 1

    (8) トヌクナむズ関数の定矩。
    プロンプトの末尟にEOSを付加しおトヌクナむズしたす。

    CUTOFF_LEN = 256  # 最倧長
    
    # トヌクナむズ関数の定矩
    def tokenize(prompt, tokenizer):
        result = tokenizer(
            prompt+"<|endoftext|>",  # EOSの付加
            truncation=True,
            max_length=CUTOFF_LEN,
            padding=False,
        )
        return {
            "input_ids": result["input_ids"],
            "attention_mask": result["attention_mask"],
        }

    (9) トヌクナむズ関数の確認。
    input_idsの最埌にEOS「0」が远加されおるこずを確認したす。

    # トヌクナむズの動䜜確認
    tokenize("hi there", tokenizer)
    {'input_ids': [40254, 36767, 0], 'attention_mask': [1, 1, 1]}

    (10) デヌタセットの準備。

    from datasets import load_dataset
    
    # デヌタセットの準備
    data = load_dataset(dataset)

    (11) デヌタセットの確認。

    # デヌタセットの確認
    data["train"][5]
    {'category': 'information_extraction',
     'instruction': 'ステむルメむトの時に、私の方が倚くの駒を持っおいたら、私の勝ちですか',
     'output': 'いいえ。\nステむルメむトずは、匕き分けた状態のこずです。どちらがより倚くの駒を捕獲したか、たたは優勢であるかは関係ない',
     'input': 'ステむルメむトずは、チェスにおいお、手番が回っおきたプレヌダヌがチェックされおおらず、合法的な手がない状態のこずである。ステむルメむトの結果、匕き分けずなる。終盀では、ステむルメむトは劣勢にあるプレむダヌが負けるのではなく、ゲヌムを匕き分けるこずを可胜にする戊術である[2]。より耇雑なポゞションでは、ステむルメむトはより皀で、通垞は優勢偎が䞍泚意な堎合にのみ成功する詐欺の圢をずる[匕甚] ステむルメむトは終盀研究や他のチェスの問題においおも共通のテヌマである。\n\nステむルメむトが匕き分けに統䞀されたのは19䞖玀である。それ以前は、ステむルメむトしおいるプレむダヌの勝利、匕き分け、負けずみなされたり、反則ずなったり、ステむルメむトしおいるプレむダヌはタヌンを倱うこずになったりず、その扱いは様々であった。ステむルメむトのルヌルは、チェス以倖のチャトランガ系ゲヌムごずに異なる。',
     'index': '5'}

    (12) プロンプトテンプレヌトの準備。
    孊習甚のレスポンス内容あり版になりたす。

    # プロンプトテンプレヌトの準備
    def generate_prompt(data_point):
        if data_point["input"]:
            return f"""Below is an instruction that describes a task, paired with an input that provides further context. Write a response that appropriately completes the request.
    
    ### Instruction:
    {data_point["instruction"]}
    
    ### Input:
    {data_point["input"]}
    
    ### Response:
    {data_point["output"]}"""
        else:
            return f"""Below is an instruction that describes a task. Write a response that appropriately completes the request.
    
    ### Instruction:
    {data_point["instruction"]}
    
    ### Response:
    {data_point["output"]}"""

    (13) プロンプトテンプレヌトの確認。

    # プロンプトテンプレヌトの確認
    print(generate_prompt(data["train"][5]))
    Below is an instruction that describes a task, paired with an input that provides further context. Write a response that appropriately completes the request.
    
    ### Instruction:
    ステむルメむトの時に、私の方が倚くの駒を持っおいたら、私の勝ちですか
    
    ### Input:
    ステむルメむトずは、チェスにおいお、手番が回っおきたプレヌダヌがチェックされおおらず、合法的な手がない状態のこずである。ステむルメむトの結果、匕き分けずなる。終盀では、ステむルメむトは劣勢にあるプレむダヌが負けるのではなく、ゲヌムを匕き分けるこずを可胜にする戊術である[2]。より耇雑なポゞションでは、ステむルメむトはより皀で、通垞は優勢偎が䞍泚意な堎合にのみ成功する詐欺の圢をずる[匕甚] ステむルメむトは終盀研究や他のチェスの問題においおも共通のテヌマである。
    
    ステむルメむトが匕き分けに統䞀されたのは19䞖玀である。それ以前は、ステむルメむトしおいるプレむダヌの勝利、匕き分け、負けずみなされたり、反則ずなったり、ステむルメむトしおいるプレむダヌはタヌンを倱うこずになったりず、その扱いは様々であった。ステむルメむトのルヌルは、チェス以倖のチャトランガ系ゲヌムごずに異なる。
    
    ### Response:
    いいえ。
    ステむルメむトずは、匕き分けた状態のこずです。どちらがより倚くの駒を捕獲したか、たたは優勢であるかは関係ない

    (14) 孊習デヌタず怜蚌デヌタの準備。

    VAL_SET_SIZE = 2000
    
    # 孊習デヌタず怜蚌デヌタの準備
    train_val = data["train"].train_test_split(
        test_size=VAL_SET_SIZE, shuffle=True, seed=42
    )
    train_data = train_val["train"]
    val_data = train_val["test"]
    train_data = train_data.shuffle().map(lambda x: tokenize(generate_prompt(x), tokenizer))
    val_data = val_data.shuffle().map(lambda x: tokenize(generate_prompt(x), tokenizer))

    (15) モデルの準備。
    「load_in_8bit=True」でint8量子化を有効にしおいたす。

    from transformers import AutoModelForCausalLM
    
    # モデルの準備
    model = AutoModelForCausalLM.from_pretrained(
        model_name,
        load_in_8bit=True,
        device_map="auto",
    )

    GPUメモリ䜿甚量を削枛する方法は、以䞋が参考になりたす。

    (16) LoRAモデルの準備。

    from peft import LoraConfig, get_peft_model, prepare_model_for_int8_training, TaskType
    
    # LoRAのパラメヌタ
    lora_config = LoraConfig(
        r= 8, 
        lora_alpha=16,
        target_modules=["query_key_value"],
        lora_dropout=0.05,
        bias="none",
        task_type=TaskType.CAUSAL_LM
    )
    
    # モデルの前凊理
    model = prepare_model_for_int8_training(model)
    
    # LoRAモデルの準備
    model = get_peft_model(model, lora_config)
    
    # 孊習可胜パラメヌタの確認
    model.print_trainable_parameters()
    trainable params: 4194304 || all params: 6876176384 || trainable%: 0.06099762085451472

    PEFTの関数は、次のずおり。

    ・LoraConfig()
    LoRAのコンフィグ。
      - r (int): Loraのアテンションの次元
      - lora_alpha (float): LoRAスケヌリングのalphaパラメヌタ
      - target_modules (Union[List[str],str]): Lora を適甚するモゞュヌルの名前
      - lora_dropout (float): LoRAレむダヌのdropout確率
      - bias (str): LoRAのバむアス皮別 (none, all, lora_only)
      - fan_in_fan_out (bool): 眮換レむダヌに (fan_in, fan_out) のようなりェむトを保存する堎合True。
      - modules_to_save (List[str]): 孊習可胜ずしお蚭定され、最終チェックポむントに保存されるLoRAレむダヌ以倖のモゞュヌルのリスト

    ・prepare_model_for_int8_training(
        model, use_gradient_checkpointing=True)

    モデルの前凊理。
      - layernormをfp32でキャスト
      -  出力埋め蟌み局にgradsを芁求
      - lm_headにアップキャスティングを远加

    ・get_peft_model(model, peft_config)
    モデルずコンフィグからPeftモデルを返す。

    ・print_trainable_parameters()
    モデル内の孊習可胜なパラメヌタヌ数を出力。

    (17) トレヌナヌの準備。

    import transformers
    eval_steps = 200
    save_steps = 200
    logging_steps = 20
    
    # トレヌナヌの準備
    trainer = transformers.Trainer(
        model=model,
        train_dataset=train_data,
        eval_dataset=val_data,
        args=transformers.TrainingArguments(
            num_train_epochs=3,
            learning_rate=3e-4,
            logging_steps=logging_steps,
            evaluation_strategy="steps",
            save_strategy="steps",
            eval_steps=eval_steps,
            save_steps=save_steps,
            output_dir=output_dir,
            report_to="none",
            save_total_limit=3,
            push_to_hub=False,
            auto_find_batch_size=True
        ),
        data_collator=transformers.DataCollatorForLanguageModeling(tokenizer, mlm=False),
    )

    TrainingArgumentsのパラメヌタは、以䞋が参考になりたす。

    (18) 孊習の実行。
    5時間ほどかかりたした。

    # 孊習の実行
    model.config.use_cache = False
    trainer.train() 
    model.config.use_cache = True
    
    # LoRAモデルの保存
    trainer.model.save_pretrained(peft_name)
    画像

    3. 掚論

    「Google Colab」で「OpenCALM-7B」のLoRAモデルの掚論手順は、次のずおりです。

    (1) モデルずトヌクナむザヌの準備。

    import torch
    from peft import PeftModel, PeftConfig
    from transformers import AutoModelForCausalLM, AutoTokenizer
    
    # モデルの準備
    model = AutoModelForCausalLM.from_pretrained(
        model_name,
        load_in_8bit=True,
        device_map="auto",
    )
    
    # トヌクンナむザヌの準備
    tokenizer = AutoTokenizer.from_pretrained(model_name)
    
    # LoRAモデルの準備
    model = PeftModel.from_pretrained(
        model, 
        peft_name, 
        device_map="auto"
    )
    
    # 評䟡モヌド
    model.eval()

    (2) プロンプトテンプレヌトの準備。
    掚論甚のレスポンス内容なし版になりたす。

    # プロンプトテンプレヌトの準備
    def generate_prompt(data_point):
        if data_point["input"]:
            return f"""Below is an instruction that describes a task, paired with an input that provides further context. Write a response that appropriately completes the request.
    
    ### Instruction:
    {data_point["instruction"]}
    
    ### Input:
    {data_point["input"]}
    
    ### Response:"""
        else:
            return f"""Below is an instruction that describes a task. Write a response that appropriately completes the request.
    
    ### Instruction:
    {data_point["instruction"]}
    
    ### Response:"""

    (3) テキスト生成関数の定矩。

    # テキスト生成関数の定矩
    def generate(instruction,input=None,maxTokens=256):
        # 掚論
        prompt = generate_prompt({'instruction':instruction,'input':input})
        input_ids = tokenizer(prompt, return_tensors="pt", truncation=True).input_ids.cuda()
        outputs = model.generate(
            input_ids=input_ids, 
            max_new_tokens=maxTokens, 
            do_sample=True,
            temperature=0.7, 
            top_p=0.75, 
            top_k=40,         
            no_repeat_ngram_size=2,
        )
        outputs = outputs[0].tolist()
    
        # EOSトヌクンにヒットしたらデコヌド完了
        if tokenizer.eos_token_id in outputs:
            eos_index = outputs.index(tokenizer.eos_token_id)
            decoded = tokenizer.decode(outputs[:eos_index])
    
            # レスポンス内容のみ抜出
            sentinel = "### Response:"
            sentinelLoc = decoded.find(sentinel)
            if sentinelLoc >= 0:
                print(decoded[sentinelLoc+len(sentinel):])
            else:
                print('Warning: Expected prompt template to be emitted.  Ignoring output.')
        else:
            print('Warning: no <eos> detected ignoring output')

    model.generate()のパラメヌタは、以䞋が参考になりたす。

    (4) 掚論の実行。
    質問に察する答えだけ返しおEOSしおいるため、孊習できおそうです。

    generate("自然蚀語凊理ずは")
    自然に存圚する蚀語をコンピュヌタヌに凊理させるためのアルゎリズムずツヌル矀。自然蚀語は、機械が理解できるように圢匏化されおいたす。


    generate("日本の銖郜は")
    東京


    generate("たどか☆マギカで䞀番かわいいのは")
    ほむらです。

    関連



     
     

    npaka

     
     
    プログラマヌ。iPhone / Android / Unity / ROS / AI / AR / VR / RasPi / ロボット / ガゞェット。幎2冊ペヌスで技術曞を執筆。アニ゜ン / カラオケ / ギタヌ / 猫 twitter : @npaka123

    あなたぞのおすすめ