以下の内容はhttps://nowokay.hatenablog.com/entry/2025/11/29/143619より取得しました。


LINEのベース日本語言語モデルを強化学習で対話できるようにして賢さを評価する(過去下書き放出)

下書きにあったものをとりあえず放出。 2023年8月おわりくらいの下書き。無加工なので、組織名などが当時のものになっています。

ココカラ。

LINEのNLP Foundation Devチームから36億パラメータの日本語言語モデルが公開されています。
https://engineering.linecorp.com/ja/blog/3.6-billion-parameter-japanese-language-model

そして、対話用にチューニングしたモデルも出ています。
https://engineering.linecorp.com/ja/blog/3.6b-japanese-language-model-with-improved-dialog-performance-by-instruction-tuning

なのだけど、対話モデルが出る前に、ベースモデルを調整して対話できるようにして、あとlm-evaluation-harnessで性能評価もしてみてたので、まとめておきます。

Instructionチューニング

言語モデルは、文章の続きを生成する仕組みになっています。 なので例えば「言語モデルは、」を与えると、「言語モデルは、音声を解釈して、 音声を復号化する。」のような文章が生成されます。

LLMでのチャットの多くは、次のような対話履歴を渡して続きを生成させるようになっています。

ユーザー: 日本の首都は?
システム: 東京です。
ユーザー: 東京には何がある?
システム:

けれども、これを対話用にチューニングされてないベースモデルに渡すと、次のように勝手に続きの対話を埋めていきがちです。もちろん、たまたまちゃんと返答になることもあります。

そこで対話用の返答のやりかたを仕込んであげる必要があるのですが、そのようなチューニングをInstructionチューニングと呼ぶようです。
LINEの対話モデルもInstructionチューニングされていますが、今回はそれを自分でやってみようという感じ。

Instructionチューニング用データセット

そうすると、学習のためのデータセットをどうするかということになります。 ここで、DatabricksがEleutherAIのpyhiaをInstructionチューニングした[Dolly 2.0]があります。
Free Dolly: Introducing the World's First Open and Commercially Viable Instruction-Tuned LLM - The Databricks Blog

このチューニングに使われたデータセットが公開されています。
databricks/databricks-dolly-15k · Datasets at Hugging Face

そして、このデータセットをkunishouさんが日本語化されていますので、これを使いましょう。
kunishou/databricks-dolly-15k-ja · Datasets at Hugging Face

LoRAファインチューニング

ということでチューニングをするのだけど、36億パラメータを全部更新するようなファインチューニングには読み込んだパラメータの数倍のVRAMが必要になります。
そんなメモリはない! そこで、パラメータの一部を更新していい感じにチューニングを行うLoRAというテクニックを使います。
といっても、PEFT(Parameter-Efficient Fine-Tuning)というライブラリがあるのでそれを使うだけだけど。
https://github.com/huggingface/peft

ただ、PEFTではどこのパラメータを更新するのか指定する必要があります。
それを確認するためにこういうコードを動かす。モデルを読み込んで表示するだけ。

from transformers import AutoModelForCausalLM
model_name = "line-corporation/japanese-large-lm-3.6b"
model = AutoModelForCausalLM.from_pretrained(
    model_name, device_map='cpu')
print(model)

こんな感じに表示されます。GPTNeoXというモデルになってることがわかります。

GPTNeoXForCausalLM(
  (gpt_neox): GPTNeoXModel(
    (embed_in): Embedding(51200, 3072)
    (emb_dropout): Dropout(p=0.0, inplace=False)
    (layers): ModuleList(
      (0-29): 30 x GPTNeoXLayer(
        (input_layernorm): LayerNorm((3072,), eps=1e-05, elementwise_affine=True)
        (post_attention_layernorm): LayerNorm((3072,), eps=1e-05, elementwise_affine=True)
        (post_attention_dropout): Dropout(p=0.0, inplace=False)
        (post_mlp_dropout): Dropout(p=0.0, inplace=False)
        (attention): GPTNeoXAttention(
          (rotary_emb): GPTNeoXRotaryEmbedding()
          (query_key_value): Linear(in_features=3072, out_features=9216, bias=True)
          (dense): Linear(in_features=3072, out_features=3072, bias=True)
          (attention_dropout): Dropout(p=0.0, inplace=False)
        )
        (mlp): GPTNeoXMLP(
          (dense_h_to_4h): Linear(in_features=3072, out_features=12288, bias=True)
          (dense_4h_to_h): Linear(in_features=12288, out_features=3072, bias=True)
          (act): GELUActivation()
        )
      )
    )
    (final_layer_norm): LayerNorm((3072,), eps=1e-05, elementwise_affine=True)
  )
  (embed_out): Linear(in_features=3072, out_features=51200, bias=False)
)

LoRAでは基本的にアテンションの線形レイヤーを対象にすればいいということなのでLinearを探します。(attention)というところを見ると(query_key_value)(dense)という層があるのがわかります。そして、だいたい`(query_key_value)'だけを対象にすればいいっぽい。

import torch
import datasets
from transformers import AutoTokenizer, AutoModelForCausalLM, Trainer, TrainingArguments
from peft import get_peft_model, LoraConfig, TaskType, PeftModel, PeftConfig

model_name = "line-corporation/japanese-large-lm-1.7b"
peft_model_name = "peft_model"
dataset_name = "kunishou/databricks-dolly-15k-ja"
targets = ["query_key_value"]

prompt_template_cqa = """ユーザー: 次の情報を元に質問に答えてください。{input}
システム: わかりました。
ユーザー: {instruction}
システム: """
prompt_template_oqa = """ユーザー: {instruction}
システム: """

def encode(sample):
    if (sample["input"]):
      prompt = prompt_template_cqa.format(instruction=sample["instruction"], input=sample["input"])
    else:
      prompt = prompt_template_oqa.format(instruction=sample["instruction"])
    target = sample["output"] + tokenizer.eos_token
    input_ids_prompt, input_ids_target = tokenizer([prompt, target]).input_ids
    input_ids = input_ids_prompt + input_ids_target
    labels = input_ids.copy()
    labels[:len(input_ids_prompt)] = [-100] * len(input_ids_prompt)
    return {"input_ids": input_ids, "labels": labels}

def get_collator(tokenizer, max_length):
    def collator(batch):
        batch = [{ key: value[:max_length] for key, value in sample.items() } for sample in batch ]
        batch = tokenizer.pad(batch, padding=True)
        batch["labels"] = [ e + [-100] * (len(batch["input_ids"][0]) - len(e)) for e in batch["labels"] ]
        batch = { key: torch.tensor(value) for key, value in batch.items() }
        return batch
    return collator

# prepare dataset
tokenizer = AutoTokenizer.from_pretrained(model_name, use_fast=False)

dataset = datasets.load_dataset(dataset_name)
dataset = dataset.map(encode)
dataset = dataset["train"].train_test_split(0.2)
train_dataset = dataset["train"]
val_dataset = dataset["test"]

# load model
base_model = AutoModelForCausalLM.from_pretrained(model_name, device_map={"": 0}, torch_dtype=torch.float16)

peft_config = LoraConfig(
    task_type=TaskType.CAUSAL_LM,
    inference_mode=False,
    target_modules=targets,
    r=16,
    lora_alpha=32,
    lora_dropout=0.05
)

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

training_args = TrainingArguments(
    output_dir="./train_results",
    learning_rate=2e-4,
    per_device_train_batch_size=4,
    gradient_accumulation_steps=4,
    per_device_eval_batch_size=16,
    num_train_epochs=1,
    logging_strategy='steps',
    logging_steps=10,
    save_strategy='epoch',
    evaluation_strategy='epoch',
    load_best_model_at_end=True,
    metric_for_best_model="eval_loss",
    greater_is_better=False,
    save_total_limit=2
)

trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=train_dataset,
    eval_dataset=val_dataset,
    data_collator=get_collator(tokenizer, 512)
)

trainer.train()
model = trainer.model
model.save_pretrained(peft_model_name)

評価

https://github.com/Stability-AI/lm-evaluation-harness

https://github.com/Stability-AI/lm-evaluation-harness/blob/jp-stable/docs/prompt_templates.md

ユーザー: 与えられた選択肢の中から、最適な答えを選んでください。
システム: 分かりました。
ユーザー: 質問:{question}
選択肢:
- {choice0}
- {choice1}
...
- {choice4}
システム: {answer}

open file in utf-8 by kishida · Pull Request #11 · shunk031/huggingface-datasets_JGLUE · GitHub

Set pad_token_id for hugging face model to suppress warning by kishida · Pull Request #82 · Stability-AI/lm-evaluation-harness · GitHub

make dir only if directory is specified in 'output_path' by kishida · Pull Request #79 · Stability-AI/lm-evaluation-harness · GitHub

3 epochでチューニング

先ほどは各データ1回ずつという1 epochで学習を行ったのだけど、3 epochほどまわしてみました。
9時間半かかって終了。

A100 80GBを8台使い、3epoch学習しました。1epochの学習にかかる時間はおおむね10分程度でした

というのを見ると「GPU力こそパワー」というのを実感しますね。

LoRAモデルのマージ

# https://note.com/__olender/n/n7913ac32c18c#f6bc06e7-c594-42ff-a8f6-72c1a5aab972
import torch
from peft import PeftConfig, PeftModel
from transformers import AutoModelForCausalLM, AutoTokenizer

model_name = "line-corporation/japanese-large-lm-3.6b"
peft_name = "lora/dolly-line-3.6b"

output_dir = peft_name + "-merged"

# PEFT(LoRA)の指定
peft_config = PeftConfig.from_pretrained(peft_name)
print (f"lora loaded {peft_name}")
# ベースモデルの読み込み
model = AutoModelForCausalLM.from_pretrained(
    model_name,
    return_dict=True,
    torch_dtype=torch.float16,
)
print (f"model loaded {model_name}")
# Rinnaのトークナイザーでは、「use_fast=False」も必要になる
try:
    tokenizer = AutoTokenizer.from_pretrained(model_name, use_fast = False)
except ValueError:
    tokenizer = AutoTokenizer.from_pretrained(model_name)

# PEFT(LoRA)の読み込み
model = PeftModel.from_pretrained(model, peft_name)
# マージモデル作成
merged_model = model.merge_and_unload()
# 出力
merged_model.save_pretrained(output_dir)  
tokenizer.save_pretrained(output_dir)
print(f"Saving to {output_dir}")  

RLHF

https://huggingface.co/datasets/kunishou/hh-rlhf-49k-ja




以上の内容はhttps://nowokay.hatenablog.com/entry/2025/11/29/143619より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14