Llama3.1 8BモデルにLoraとDPOアダプタの接続したものをまとめてHuggingface保存する方法

未分類

この記事では、Llama3.1 8B Instructモデルに作成済みのLoRAアダプタとDPOアダプタを統合し、最終的にHugging Face Hubにアップロードする方法を詳しく解説します。

必要なライブラリのインストール

まずは、必要なライブラリをインストールします。以下のコマンドを実行してください。

!pip install -U ipywidgets
!pip install transformers==4.46.3
!pip install -U bitsandbytes
!pip install -U accelerate
!pip install -U datasets
!pip install -U peft
!pip install huggingface_hub

また、必要なモジュールをインポートし、Hugging Faceにログインします。

from huggingface_hub import login
from transformers import (
    AutoModelForCausalLM,
    AutoTokenizer,
    BitsAndBytesConfig,
)
from peft import PeftModel
from huggingface_hub import HfApi
import torch

# HuggingFaceログイン
HF_TOKEN = "xxxxxxxxxxxxxxx"

モデルの読み込みとアダプタの結合

Llama3.1 8B InstructモデルにLoRAアダプタとDPOアダプタを結合する手順を説明します。

# 推論モードで保存
import os
from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import PeftModel, PeftConfig

# モデルとトークナイザーのロード
base_model_name = "meta-llama/Llama-3.1-8B-Instruct"  # ベースモデル名を指定
model = AutoModelForCausalLM.from_pretrained(base_model_name, torch_dtype="auto")
tokenizer = AutoTokenizer.from_pretrained(base_model_name)

# LoRA アダプターの適用
lora_adapter_name = "kota-kawa/Llama-3.1-8B-Instruct_Freedom_lora"
lora_config = PeftConfig.from_pretrained(lora_adapter_name)
model = PeftModel.from_pretrained(model, lora_adapter_name)

# DPO アダプターの適用
dpo_adapter_name = "kota-kawa/Llama-3.1-8B-Instruct-Freedom-DPO"
dpo_config = PeftConfig.from_pretrained(dpo_adapter_name)
model = PeftModel.from_pretrained(model, dpo_adapter_name)

# 統合して余分なパラメータを削除
model = model.merge_and_unload()  # LoRA と DPO を統合

# 推論モードに設定
model.eval()

# モデルとトークナイザーの保存先ディレクトリを指定
save_path = "./llama3_combined_model_merged"
os.makedirs(save_path, exist_ok=True)

# モデルを保存
model.save_pretrained(save_path, safe_serialization=True)  # safetensors 形式で保存, safe_serialization=True
tokenizer.save_pretrained(save_path)

# モデルの設定を取得し、手動で保存
config = model.config
config.save_pretrained(save_path)

# 統合後のモデルの状態を確認
print(model.state_dict().keys())

safetensorsファイルの分割&model.safetensors.index.jsonの保存

import os
import torch
import json
from safetensors.torch import save_file

# 保存ディレクトリ
save_path = "./llama3_combined_model_merged"
os.makedirs(save_path, exist_ok=True)

# モデルの state_dict を取得
state_dict = model.state_dict()

# パラメータを分割
keys = list(state_dict.keys())
num_parts = 4  # 分割する数
split_size = len(keys) // num_parts

# 保存ファイル名のテンプレート
file_template = "model-{index:05d}-of-{total:05d}.safetensors"

# 分割と保存
weight_map = {}  # インデックス用マッピング
for i in range(num_parts):
    part_keys = keys[i * split_size:(i + 1) * split_size]
    part_dict = {key: state_dict[key] for key in part_keys}
    file_name = file_template.format(index=i + 1, total=num_parts)
    part_path = os.path.join(save_path, file_name)
    save_file(part_dict, part_path)
    print(f"Saved part {i + 1} to {file_name}")
    # インデックスに追加
    for key in part_keys:
        weight_map[key] = file_name

# 最後の分割に余ったキーを追加
if len(keys) % num_parts != 0:
    extra_keys = keys[num_parts * split_size:]
    extra_dict = {key: state_dict[key] for key in extra_keys}
    extra_file_name = file_template.format(index=num_parts + 1, total=num_parts + 1)
    extra_path = os.path.join(save_path, extra_file_name)
    save_file(extra_dict, extra_path)
    print(f"Saved extra part to {extra_file_name}")
    # インデックスに追加
    for key in extra_keys:
        weight_map[key] = extra_file_name

# インデックスファイルを生成
index_dict = {
    "metadata": {},
    "weight_map": weight_map
}

index_file_path = os.path.join(save_path, "model.safetensors.index.json")
with open(index_file_path, "w", encoding="utf-8") as f:
    json.dump(index_dict, f, indent=2, ensure_ascii=False)

print("All parts and index file have been saved successfully.")

base_model.model. という文字がファイルの中身の先頭に入ってしまうので、それを削除

現在作成されたファイルにはすべての行の先頭に「base_model.model.」がついてしまい、これだとvllmでエラーになってしまうので、その部分を削除していきます。

import os
import re
import json
from safetensors.torch import load_file, save_file

def process_safetensors(directory):
    # 対象ディレクトリ内のすべてのファイルを探索
    for filename in os.listdir(directory):
        file_path = os.path.join(directory, filename)

        if filename.endswith(".safetensors") and filename != "model.safetensors.index.json":
            # ---- 1) safetensors ファイルを読み込み ----
            print(f"処理中: {file_path}")
            state_dict = load_file(file_path)

            # ---- 2) base_model.model. プレフィックスを削除 ----
            new_state_dict = {}
            for key, value in state_dict.items():
                new_key = re.sub(r"^base_model\.model\.", "", key)
                new_state_dict[new_key] = value

            # ---- 3) 上書き保存 ----
            save_file(new_state_dict, file_path)
            print(f"上書き保存完了: {file_path}")

        elif filename == "model.safetensors.index.json":
            # ---- インデックスファイルを処理 ----
            print(f"インデックスファイル処理中: {file_path}")
            with open(file_path, "r", encoding="utf-8") as f:
                index_dict = json.load(f)

            # weight_map のキーを修正
            new_weight_map = {}
            for key, value in index_dict["weight_map"].items():
                new_key = re.sub(r"^base_model\.model\.", "", key)
                new_weight_map[new_key] = value

            index_dict["weight_map"] = new_weight_map

            # 修正済みインデックスファイルを上書き保存
            with open(file_path, "w", encoding="utf-8") as f:
                json.dump(index_dict, f, indent=2, ensure_ascii=False)
            print(f"インデックスファイルの上書き保存完了: {file_path}")

# 実行ディレクトリを指定
process_directory = "./llama3_combined_model_merged"
process_safetensors(process_directory)

名前を付けてHuggingFaceにアップロード

最後に、モデルをHugging Face Hubにアップロードします。

# 6. Hugging Face Hub にアップロード
from huggingface_hub import HfApi

api = HfApi()
repo_id = "kota-kawa/Llama-3.1-8B-Instruct-Freedom_v3"  # 適切なリポジトリ名
api.create_repo(repo_id=repo_id, repo_type="model")

api.upload_folder(
    folder_path=save_path,
    repo_id=repo_id,
    repo_type="model"
)

print(f"モデルが Hugging Face Hub にアップロードされました: https://huggingface.co/{repo_id}")

最後に

これでtransformersやvllmでモデルを以下のように読み込んですぐに実行できるようになります。

from vllm import LLM, SamplingParams

# Initialize the model with PagedAttention
llm = LLM(model="kota-kawa/Llama-3.1-8B-Instruct-Freedom_v3")

しかし、元モデルを使用するよりも多くのメモリを消費してしまうので、以下のように工夫をして実行すると良いと思います。

from vllm import LLM, SamplingParams

# Initialize the model with PagedAttention
llm = LLM(model="kota-kawa/Llama-3.1-8B-Instruct-Freedom_v3", max_model_len=1024, max_num_seqs=1)

モデルとアダプターを1つのモデルとして結合するよりも、元モデルにプログラムで結合するほうがメモリは節約できます。

コメント