LightChatAssistant 2x7B を再現する

  • 前回Chat Vectorについて簡単に予習したので、とりあえず「LightChatAssistant 2x7B」の作成手順を再現してみたいと思います。
  • 作者さんがモデルカードで丁寧に説明してくださっているので、基本的にそれをなぞるだけです。まずはまったく同じモデルを作ってみます。

huggingface.co

Chat Vectorによる処理

  • 以降、Google ColabのCPU(ハイメモリ)で試します。3つ分のモデルファイルを扱えるだけのRAM容量さえあればよく、GPU無しで完結するようです。
  • まずは、Chat Vector処理を行うための依存関係をインストールします。ColabだとTransformersはプリインストールされてますね。
# 依存関係のインストール
!pip install accelerate protobuf
  • Chat Vectorは、追加学習したチャットモデルの重みから、対応するベースモデルの重みを差し引くことで得られる差分です。
  • ここでは、ベースモデルは"Mistral-7B-v0.1"、チャットモデルに"Mistral-7B-Instruct-v0.2"を使っています。いずれもMistralの公式モデルで、元論文に則った処理です。
from transformers import AutoModelForCausalLM
import torch

# ベースモデル="Mistral-7B-v0.1"
base_model = AutoModelForCausalLM.from_pretrained( "mistralai/Mistral-7B-v0.1", torch_dtype=torch.bfloat16, device_map="cpu", )
# 差分を取り出すチャットモデル="Mistral-7B-Instruct-v0.2"
inst_model = AutoModelForCausalLM.from_pretrained( "mistralai/Mistral-7B-Instruct-v0.2", torch_dtype=torch.bfloat16, device_map="cpu", )
  • つづいて、取り出したChat Vectorを統合するための日本語モデルを2つ用意します(あとでその2つをMoEマージします)。
  • 1つ目は"chatntq-ja-7b-v1.0"です。ChatNTQの日本語チャット性能はMistral7B系モデルの中ではやや抜けています。
  • すでに日本語でチャットファインチューンされたモデルですが、英語モデルのChat Vectorを適用してさらに強化を図る形になります。
# 差分を張り付ける日本語モデル(1)="chatntq-ja-7b-v1.0"
cp_model = AutoModelForCausalLM.from_pretrained( "NTQAI/chatntq-ja-7b-v1.0", torch_dtype=torch.bfloat16, device_map="cpu", )
  • あとは以下のようにChat Vectorを抽出して日本語モデルに貼り付けます。この手順は、モデルカードにリンクされているjovyanさんによるこちらの記事で詳しく紹介されています(大変参考になります)。
# Chat Vectorを取り出して"chatntq-ja-7b-v1.0"へコピー
for k, v in cp_model.state_dict().items(): chat_vector = inst_model.state_dict()[k] - base_model.state_dict()[k] new_v = v + ( 0.8 * chat_vector.to(v.device) ) v.copy_(new_v)
# Chat Vectorを張り付けた"chatntq-ja-7b-v1.0"を保存 cp_model.save_pretrained("./chatntq-ja-7b-v1.0-chatvector")
  • なお、元論文ではChat Vectorを掛ける強度として1.0または0.5を試しています。1.0だと差分の影響が強すぎて英語が混ざりやすくなり、逆に0.5だと差分の影響が表れにくくなる可能性があるようです。
    • おそらくそうした理由から、LightChatAssistant 2x7Bを含むいくつかのChatVectorモデルで0.8掛けという数値を採用しているのだと思います。
  • さて、もう一つの日本語モデルは小説生成モデルの"Antler-7B"です。このモデルの学習データにNSFWが含まれているのでご注意ください。
  • 手順は先ほどと同じで、今度はAntler-7BにChatVectorを掛けます。こちらは元々チャットファインチューンされていないモデルにチャット能力を付与するという、元論文の意図に近い形になっています。
# 差分を張り付ける日本語モデル(2)="Antler-7B"
cp_model2 = AutoModelForCausalLM.from_pretrained( "Elizezen/Antler-7B", torch_dtype=torch.bfloat16, device_map="cpu", )
# Chat Vectorを取り出して"Antler-7B"へコピー
for k, v in cp_model2.state_dict().items(): chat_vector = inst_model.state_dict()[k] - base_model.state_dict()[k] new_v = v + ( 0.8 * chat_vector.to(v.device) ) v.copy_(new_v)
# Chat Vectorを張り付けた"Antler-7B"を保存
cp_model2.save_pretrained("./Antler-7B-chatvector")
  • これで2つのChat Vector適用済みモデルが用意できました。
  • Tokenizerはそれぞれの日本語モデルから以下のように流用します。
from transformers import AutoTokenizer

cp_tokenizer = AutoTokenizer.from_pretrained("NTQAI/chatntq-ja-7b-v1.0")
cp_tokenizer.save_pretrained("./chatntq-ja-7b-v1.0-chatvector")

cp_tokenizer2 = AutoTokenizer.from_pretrained("Elizezen/Antler-7B")
cp_tokenizer2.save_pretrained("./Antler-7B-chatvector")
  • 最後に、各モデルのconfig.jsonの修正が必要です。
  • 差分を取り出した"Mistral-7B-Instruct-v0.2"のコンテキスト関係の設定値が他の"Mistral-7B-v0.1"系モデルとは異なっているために必要な処理のようです。
    • "Mistral-7B-Instruct-v0.2"は、v0.1系モデルで採用していたSliding Windowという仕組みが無効になっています。
    • そのため、Chat Vector適用したモデルも"Mistral-7B-Instruct-v0.2"のコンテキスト設定に合わせる必要が生じます。
  • 具体的には、"max_position_embeddings" = 32768、"rope_theta" = 1000000.0、"sliding_window" = None、に修正します。
  • Claudeに投げたところ、以下のような修正用のコードを作ってくれました。
import json

with open("./chatntq-ja-7b-v1.0-chatvector/config.json", "r", encoding="utf-8") as f:
    config = json.load(f)

config["max_position_embeddings"] = 32768
config["rope_theta"] = 1000000.0
config["sliding_window"] = None

with open("./chatntq-ja-7b-v1.0-chatvector/config.json", "w", encoding="utf-8") as f: json.dump(config, f, indent=2, ensure_ascii=False) with open("./Antler-7B-chatvector/config.json", "r", encoding="utf-8") as f: config = json.load(f) config["max_position_embeddings"] = 32768 config["rope_theta"] = 1000000.0 config["sliding_window"] = None with open("./Antler-7B-chatvector/config.json", "w", encoding="utf-8") as f: json.dump(config, f, indent=2, ensure_ascii=False)
  • ちなみに、最初に試したときにこのconfig.jsonの処理をスキップしてしまったのですが、結果的にモデル出力の質がかなり劣化する形となりました。どうやらロングコンテキストの処理に影響するだけではないようです。
  • 以上で、Chat Vector関係の処理は終わりです。

MergeKitによるMoEマージ

  • では先ほど作成した2つのChat Vector適用モデルをMergeKitでマージします。
  • 通常版のMergeKitではMoEマージに対応していないので、Mixtralブランチを指定して導入する必要があります。
  • MoEマージの手順については、以前も紹介したはちさんの記事で詳しく解説されているものを参考にしています。
# メモリ容量の確保
del cp_model, cp_model2

# MoE対応版のMergeKitの導入
!git clone -b mixtral https://github.com/cg123/mergekit.git
!mkdir ./mergekit/output
!mkdir ./mergekit/config

!pip install -e ./mergekit
!pip install git+https://github.com/huggingface/transformers #セッションは再起動しない
  • なおColabでは、途中でセッションの再起動を促されますが無視してキャンセルします。
  • MoEマージのためのConfigは、LightChatAssistant-2x7BのRepoに上がっていますので、ここではそのまま借用します。内容的にNSFWのためご留意ください。
# LightChatAssistant-2x7BのRepoからマージ設定をDL(NSFWにつき注意)
!wget https://huggingface.co/Sdff-Ltba/LightChatAssistant-2x7B/resolve/main/mergekit_moe_config.yml -P ./mergekit/config
  • あとは2モデルのマージを実行するだけです。完了後、outputフォルダに2x7Bのモデルファイルが格納されます。
# MoEマージの実行
!python ./mergekit/mergekit/scripts/mixtral_moe.py ./mergekit/config/mergekit_moe_config.yml ./mergekit/output -v

llama.cppによる量子化

  • 以上で「LightChatAssistant 2x7B」の作成手順の再現が完了しました。
  • とはいえ実際にローカルでモデルを使用する場合、Transformersでそのまま使う人はまずいないと思いますので、llama.cppのGGUFフォーマットに変換しておきます。
# llama.cppの導入(依存関係はモデル変換用のみインストール)
!git clone https://github.com/ggerganov/llama.cpp
!pip install -r ./llama.cpp/requirements/requirements-convert.txt

# Q8_0量子化
!python ./llama.cpp/convert.py ./mergekit/output --outfile LightChat-2x7B-Repro-q8.gguf --outtype q8_0
  • Q8量子化後で、12.8GBほどのファイルサイズになりました。より圧縮する場合は、ここでf16に変換した後で(q8でもいいと思いますが)、さらにquantizeコマンドで希望の量子化タイプに変換することになりますね。
  • あとは一応Google Driveに保存しておきます。ギリギリ空きがありました。
from google.colab import drive
drive.mount('/content/drive')

!cp LightChat-2x7B-Repro-q8.gguf /content/drive/MyDrive/LightChat-2x7B-Repro-q8.gguf

再現モデルのテスト

  • 再現したモデルを、元の「LightChatAssistant 2x7B」と比較してみます。
  • 再現モデルを、たびたび利用している「ELYZA-tasks-100」ベンチマークにかけてGPT-4に評価させてみました。
名称 LightChatAssistant
2x7B【オリジナル】
LightChatAssistant
2x7B【再現】
ChatNTQ JA 7B
パラメータ 2x7B 2x7B 7B
量子化タイプ Q8_0 Q8_0 Q8_0
ElyzaTasks100
スコア平均
3.31 3.29 3.06
標準誤差 0.14 0.15 0.15
平均回答字数 273.61 277.00 240.46
  • スコアの差も回答字数の差も十分に小さく、誤差の範疇にとどまっています。
  • ということで、無事に「LightChatAssistant 2x7B」の再現ができたようです。詳細な作成手順を公開し、再現を可能にしてくださった作者さんにあらためて感謝申し上げます。ありがとうございます。

Chat Vectorの差分抽出モデルを入れ替えたり、MoEマージの手法を変更することで、いろいろと試行錯誤して楽しむことができそうです。

 

【LLM論文を読む】Chat Vector:LLMに新たな言語での指示追従とアラインメントを付与するシンプルなアプローチ

  • 前回の記事につづき、「LightChatAssistant 2x7B」に高いチャット性能をもたらした要素について調べています。
  • 基本的には、1)ベースモデルとしての「ChatNTQ JA 7B」の日本語性能の高さ、2) MoEマージで総パラメータ数が増えることによる全般的な性能向上、3) 小説生成モデル「Antler-7B」とのマージによるモデル特長の相互補完、の3点でかなりの部分が説明できそうです。
  • 一方で、モデル作者のSdff-Ltbaさんはマージ前のモデルに対してChat Vectorという処理を施し、チャット能力を高めたと説明されています。よって、Chat Vectorがどの程度の性能向上に寄与しているのかもざっくり知りたい気がします。
  • さしあたって、そもそもChat Vectorとは何かについて簡単に予習しておきたいと思います。元のarXiv論文は、2023年10月に台湾の研究グループから投稿されています。

arxiv.org

要旨

近年、オープンソースの大規模言語モデル(LLM)の開発が急速に進んでいる。しかしながら、データの制約から、オープンソースのLLMの多くは主に英語に特化している。この問題に対処するため、我々はChat Vectorという概念を導入し、単純なモデル演算によって、事前学習された言語モデルに指示追従と人間の価値観のアラインメントを付与させる。Chat Vectorは、事前に訓練されたベースモデル(例えばLLaMA2)の重みから、対応するチャットモデル(例えばLLaMA2-chat)の重みを引くことで得られる。継続的に事前訓練されたモデルの重みにChat Vectorを追加するだけで、さらなる訓練を必要とせずに、新しい言語でのチャット機能をモデルに与えることができる。我々の実証的研究は、3つの異なる側面からChat Vectorの優れた有効性を実証している:すなわち、指示追従、毒性緩和、マルチターン対話である。さらに、本アプローチの適応性を示すために、様々な言語、ベースモデル、Chat Vectorを包含するように実験を拡張した。その結果、Chat Vectorのシンプルさ、有効性、適用範囲の広さが強調され、事前に学習された言語モデルにおいて会話機能を効率的に実現するための説得力のあるソリューションであることが分かった。

メモ

  • Sdff-Ltbaさんがモデルカードにリンクされているこちらの記事に、概要から手順までわかりやすく解説されていました。半月ほど前にかなり話題になっていたんですね。

qiita.com

  • 簡単に言うと「深層学習モデルの重みに対して足し算引き算が成り立つ」という前提から、英語チャットモデルの「チャット能力」に関するベクトルを取り出し、別の言語のチャットモデルに適用する、という手法だそうです。

ほかに元論文に目を通して気になった点も列挙します。

  • すでに「Task Vector」というアプローチがあり(微調整済みモデルの重みから事前学習済みモデルの重みを引き算して得る)、この研究ではこれを英語から別言語へのチャット能力転移に利用した。
  • Vicunaベンチマークで指示追従能力をテストし、以下のことが確認された。
    • llama2 → 継続事前学習 + Chat Vectorは、繁体中国語LLaMAとChineseLLaMAの両方において、llama2 → 継続事前学習 → ファインチューンと同等の結果を示した。
    • ファインチューンとChat Vectorは相補的な効果がある: FTとChat Vectorを一緒に行うことで、どちらか一方だけを行うよりも良いパフォーマンスが得られる。
    • llama2-chat → 継続事前学習 + FT はチャットの能力を失わせる:llama2 → 継続事前学習 → FT よりも性能が良いが、llama2 CP + Chat Vectorや llama2 → 継続事前学習 → FT + Chat Vectorよりも性能が悪い。これは、llama2-chat → 継続事前学習 + FTは情報損失を引き起こすことを示しており、また、Chat Vectorを使用することの重要性を示している。

まとめ

  • Chat Vectorは単体でも効果があるものの、ファインチューンと併用するほうがよいみたいです。「LightChatAssistant 2x7B」では、片方がチャットファインチューンモデルの「ChatNTQ JA 7B」です(もう片方は小説ファインチューンモデルの「Antler-7B」)。
  • ChatVectorを取り出す英語チャットモデルについて、元論文ではMistralモデルに関しては公式の「Mistral Instruct v0.2」を使っていて、これは「LightChatAssistant 2x7B」でも踏襲されています。
  • なお、チャットモデルをベースにしてChatVectorをかけると情報損失が起きるリスクがあるので、ベースにはあくまで共通のベースモデルを使うべきとしています。「LightChatAssistant 2x7B」ではおおもとの「Mistral-7B-v0.1」をベースに使っています。
  • ChatVectorの重みをどれらい強くかけるかはケースバイケースのようで(論文で使われているのは1.0と0.5)、1でかけると出力に英語が混ざったりするようです。「LightChatAssistant 2x7B」では0.8を採用しています。

ということで、次回はChatVectorを使って実際に「LightChatAssistant 2x7B」を再現してみたいと思います。

 

 

MergeKitによるMoEマージだけで日本語性能は上がるか?

  • 前回の記事で、「LightChatAssistant 2x7B」が日本語チャットモデルのベンチマークでもかなり高いスコアを示すことを確認しました(スコア上はCohereの「Command-R 35B」に近い水準です)。
  • 体感的にもベンチマーク的にも優れた日本語チャットモデルで、サイズが小さいことも考えるととても魅力的です。

sc-bakushu.hatenablog.com

  • 作者さんがモデルカードで説明されている制作プロセスを読むと、単純に「ChatNTQ 7B」と「Antler-7B」をMoEマージする前に、ChatVectorという手法により各モデルに微調整を施しているようです。
  • となると、「LightChatAssistant 2x7B」の性能の上昇はMoEによる部分が大きいのか、あるいはChatVectorによる部分が大きいのかが気になってきます。
  • というわけで、まずChatVectorを使わない単純なMoEマージを試して、MoEオンリーでどれくらいの性能上昇が得られるのかを確認してみたいと思います。

ChatNTQ 7B x Antler 7B の単純なMoE

  • 最初に、「LightChatAssistant 2x7B」の制作プロセスのうちChatVectorによる処理の部分だけスキップして、単純なMoEモデルを作ります。
  • なお、MoEマージの手順については、はちさんによる以下の記事を参考にさせていただきました。ありがとうございます。

note.com

  • 結果は以下のとおりです。2モデルの単純な直マージだと「Chat NTQ 7B」からの性能向上はありません。誤差を踏まえるとほぼ同じと言っていいと思います。
名称 LightChatAssistant
2x7B

Chat NTQ JA 7B x Antler 7B 直マージ

(CheapEmbed)

ChatNTQ JA 7B
パラメータサイズ 2x7B 2x7B 7B
量子化タイプ Q8_0 Q8_0 Q8_0
ElyzaTasks100
スコア平均
3.31 3.04 3.06
標準誤差 0.14 0.14 0.15
  • ただし実際の出力をみると、日本語の流暢性はいくらか改善している印象があり、小説生成モデル「Antler-7B」の影響はわずかに感じられます。
  • ところで、このMoEでは「LightChatAssistant 2x7B」のマージ設定でも使われている「cheap_embed」というゲートモードを使いましたが、どうやらこのモードの選択がけっこう重要なようです。
  • 次は「random」というモードに変更して、あらためてマージを試してみます。結果は以下のとおりでした。
名称 LightChatAssistant
2x7B
Chat NTQ JA 7B x Antler 7B 直マージ
(Random)
ChatNTQ JA 7B
パラメータサイズ 2x7B 2x7B 7B
量子化タイプ Q8_0 Q8_0 Q8_0
ElyzaTasks100
スコア平均
3.31 3.15 3.06
標準誤差 0.14 0.14 0.15
  • 「random」へ変更することで、スコアに改善が見られました。まだ誤差の範囲内ですが、体感的にはわずかに差がありそうです。とはいえ本家の「LightChatAssistant 2x7B」には及びません。
  • MergeKitに関するウェブ上の解説を斜め読みすると、「random」というモードは一番単純なマージ設定のようで、「cheap_embed」や「hidden」のように各モデルの特長の違いを考慮しない雑なモードのようです。

docs.google.com

  • ということは、純粋にパラメータ数が増えるだけでも日本語性能が上がる余地があるのかもしれない、という気がしてきます。

ChatNTQ 7B x ChatNTQ 7B のダブルMoE

  • 「Antler-7B」は小説生成モデルなので、チャットモデルである「Chat NTQ 7B」と性能補完的ではあるものの、それ自体のチャット性能は(微調整しない限り)低いままです。
  • では、「Antler-7B」を使わずに「Chat NTQ 7B」を2つ使ってMoEマージするとどうなるのでしょうか?
  • MergeKitの説明などを見ると、一般的には「同一モデルのマージは意味がない」とされているようですが、物は試しでやってみます。結果は以下の通りです。
名称

LightChat

Assistant

2x7B

Chat NTQ JA 7B x Chat NTQ JA 7B 直マージ(Random) Chat NTQ JA 7B x
Antler 7B 直マージ
(Random)
ChatNTQ JA 7B
パラメータ 2x7B 2x7B 2x7B 7B
量子化タイプ Q8_0 Q8_0 Q8_0 Q8_0
ElyzaTasks100
スコア平均
3.31 3.25 3.15 3.06
標準誤差 0.14 0.14 0.14 0.15
  • 意外なことに、かなりスコアが改善しています。「LightChatAssistant 2x7B」ほどではありませんが、明らかに性能が上がりました。
  • 実際の出力をみると「LightChatAssistant 2x7B」に比べて日本語の流暢性は劣っており面白味はありませんが、計算などの推論能力に多少の向上が見られました。

感想

  • ということで、「Chat NTQ 7B」→「LightChatAssistant 2x7B」の顕著な性能向上のうち、ざっくり1/3~半分程度はMoEマージでパラメータ数が伸びることで説明できそうな印象です。
  • なお今回は素の「Antler-7B」を使ったので、チャットファインチューンを施していないモデルをマージに組み込むことの悪影響が出た感は否めません。
  • 次は、ChatVectorによる効果についても調べてみたいと思います。

補足

  • パラメータ数(モデルサイズ)を増やすタイプのモデルマージとしては、MoEのほかに「Passthrough」という手法も存在します。
    • MoEがコミュニティで一般化する前から使われていたいわゆる「Frankenマージ」を作成する手法です。
  • 今回、MoEマージ以外にも、この「Passthrough」など他のマージ手法も簡単に試してみたのですが、そもそもモデルの出力がかなり壊れてしまい、性能を測定するに至りませんでした。MoEマージのほうが意外に扱いやすい手法のようです。

 

ChatNTQ 7B と LightChatAssistant 2x7B の日本語性能を測定する

  • 前回の記事で試した日本語チャットモデル「ChatNTQ-JA-7B-v0.1」と、そのMoEモデル「LightChatAssistant 2x7B(改称あり)」について、かなり性能が良さそうな感触が得られたので、追加でテストしてみました。
  • LLMの日本語チャット性能を測るベンチマークとしては「MT-Bench日本語版(GPT-4測定)」が最も代表的ですが、何度も測定するにはコスト面でヘビーそうなので、代わりにELYZAさんの考案した「ELYZA-tasks-100」を使うことにしました。

huggingface.co

  • このベンチマークは5点満点の日本語記述問題100問から構成されています。本来は人手で採点しますが、GPT-4採点でも人力評価とかなり高い相関が得られるようです。
    • 詳細はこちらこちらの記事で丁寧に紹介されています。同じモデルでも記事によって若干スコアが違いますが、単純な誤差のほかGPT-4のバージョン更新などが影響するようです。

ELYZATasks100 測定結果

  • 今回はGPT-4-Turbo(0125版)を使って測定しました。APIコストは1件あたり1ドルほど。比較対象にしたのは個人的に日本語性能が高いと感じた小型モデルです。結果は以下のとおりでした。
ベース Command R Mistral 7B Llama-2 7B
名称 Command R v01 LightChat Assistant
2x7B
ChatNTQ JA 7b Rakuten 7B Chat Shisa Gamma 7B ELYZA 7B Instruct
パラメータ 35B 35B 2x7B 7B 7B 7B 7B
量子化 Q8_0 IQ3_XXS Q8_0 Q8_0 Q8_0 Q8_0 Q8_0
サイズ
(量子化後)
37.2GB 13.8GB 13.7GB 7.7GB 7.8GB 7.7GB 7.2GB

ELYZATasks
平均得点

3.42 3.09 3.31 3.06 2.82 2.81 2.46
標準誤差 0.15 0.15 0.14 0.15 0.14 0.15* 0.13
平均字数 194.02 196.58 273.61 240.46 108.54 297.71 243.02
  • ベンチマーク結果は、個人的な使用感による順位とだいたい一致した印象です。「ChatNTQ-JA-7B」は、やはり7Bクラスのモデルの中ではとても優秀でした(比較した「Rakuten 7B」や「Shisa Gamma 7B」も7Bモデルの中では性能のいい部類です)。
  • この「ChatNTQ-JA-7B」と小説生成モデル「Antler-7B」をMoEマージした「LightChat Assistant 2x7B」のスコアはさらに高いです。パラメータ数がはるかに大きい多言語モデル「Command-R 35B」の日本語性能に迫っています。
    • さらにファイルサイズがほぼ同じになる「Command-R 35B」のIQ3_XXS量子化と比較すると「LightChat Assistant 2x7B」のQ8量子化のほうがスコアが高くなります。
  • なおGPT-4は冗長な回答を高く採点しやすいとよく言われるので、興味本位で出力字数の平均も取ってみました。今回は字数に影響される傾向は見られませんでした(ELYZATasks100では採点基準で説明の丁寧さも評価されるので、字数と相関があったとしても必ずしも問題はありません)。

 雑感

  • LightChat Assistant 2x7B」のチャット性能の高さは、ベースにした「ChatNTQ-JA-7B」の優秀さもありますが、小説生成モデルの「Antler-7B」とのマージがうまく機能している面も大きいようです。
  • 「Mixtral 8x7B」や「Qwen 1.5 MoE A2.7B」のように最初からMoEを使って作られたモデルの推論が高効率・高性能なことはよく知られています。一方、MergeKitで作られたコミュニティのMoEモデル(FrankenMoE)は、サイズは大きいけれど体感性能がほとんど変わらないと感じることが多いです。
  • しかし「LightChat Assistant 2x7B」は、体感性能でもベンチマークスコアでも明らかにベースモデルを上回っているので、適切な組み合わせでマージするとMergeKit製のMoEでもしっかり効果が得られるようです。
  • 面白いので、MoEマージの組み合わせについて引き続き調べてみたいと思います。

 

Mistral 7Bベースの日本語チャットモデル ChatNTQ-JA-7B を試す

「chatntq_chatvector-MoE-Antler_chatvector-2x7Bchatntq_chatvector-MoE-Antler_chatvector-2x7B」という呪文のような日本語MoEモデルが話題になっていました。

 

Mistral 7Bベースの「Japanese Stable LM Base Gamma」をファインチューンした2つの異なるモデル(Antler 7B, ChatNTQ-JA-7B)を2x7BのMoEにしたモデルだそうです。

このMoEモデルを早速試してみたところ、確かに賢そうな印象を受けました。ただ、そもそもベースにされている2つのモデルを聞いたことがありませんでした。

Antler 7B は日本のネット小説テキストを使って学習させた小説生成モデルのようです(このモデルが学習データにNSFWコンテンツを含んでいるらしい)。日本語のライトノベル風テキストに対する潜在的需要は結構強いらしく、類似のコンセプトのモデルは他にもHuggingFaceで見かけます。

・もうひとつが本題の ChatNTQ JA 7B です。こちらは小説生成ではなく、通常の日本語チャットでファインチューンされたモデルのようです。

ChatNTQ-JA-7Bについて

モデルのアップロード日をみると、どうやら昨年末に怒涛の勢いで公開が続いた日本語ファインチューンモデルの一つで、完全に見過ごしていたようです。

リリースしたNTQ AIという会社はなんとベトナムのスタートアップらしく、日本にも拠点があると説明されています。

StabilityAIの「Japanese Stable LM Base Gamma」をベースにしているので、すでに日本語で追加学習を済ませたモデルを、追加的に微調整した形になっています。

同じく「Stable LM Base Gamma」ベースの「Shisa-gamma-7B」も、よくファインチューンされたモデルでしたが、モデルカードによると日本語MT-Benchのスコアではこれを上回ったと報告しています。

出力例

あなたは親切なAIアシスタントです。

USER: こんにちは。自己紹介をしてください!
ASSISTANT: こんにちは、私は人間が必要とする情報を提供するために開発されたAIアシスタントです。よろしくお願いします!
USER: 地理について質問です。京都府に海はありますか?
ASSISTANT: 申し訳ありません。そのようなデータが見当たらないため回答できません。もし可能であれば、別の方法で確認してみて下さい。
USER: 日本語について質問です。光陰矢の如し、とはどんな意味ですか?
ASSISTANT: 「光陰矢の如し」とは、時間が過ぎ去るのは矢のように早いという意味です。
ASSISTANT: 以上のように、様々な分野に関する質問に対応することができます。お困りの際は何なりとお申し付け下さい。

適当なプロンプトフォーマットで試しただけですが、7Bモデルであることを考えるとかなり良い印象を受けます。おそらく日本に関する知識はあまり学習できていないと思いますが、日本語チャット用途という点では筋がよさそうです。

冒頭のMoEモデルも含めて、もうすこしテストしてみたいと思います。

 

llama.cpp: CPU+GPUのプロンプト処理が高速化される(#6083)

 

今さら知ったのですが、2週間ほど前にllama.cppでモデルをGPUに部分オフロードした場合のプロンプト処理速度が大幅に向上されていました。

github.com

  • 従来のllama.cppではGPUフルオフロードから部分オフロードに移行すると急激にプロンプト処理(PP)が遅くなるため、CPU+GPUのハイブリッド推論の魅力を大きく損ねていました。
  • このPRによって、CPU比率が増えた場合のPP速度低下が緩和され、部分オフロードの実用性が大幅に改善しています。
  • 以下はMixtral 8x7B のQ3_K_L量子化モデルで1024トークンのPPを実行したときのグラフです。

  • モデルのレイヤー33層のうち半分をGPUから引き上げた場合、旧来のllama.cppではPP速度が半分以下に低下していますが、このPR適用後は2/3程度の処理速度を保っています。
  • このPRの技術的な概要について、llama.cppのJohannesGaesslerさんが以下のように説明しています。

CPUレイヤーのデータはRAMにあり、GPUレイヤーのデータはVRAMにあります。GPUレイヤーは常に GPU上で評価されます。

#PR6083では、バッチサイズが大きい場合(つまりプロンプト処理の場合)、GPUの高い計算能力を利用するためにCPUレイヤーのすべてのデータがGPUに移動され、そこで計算が行われます。バッチサイズが小さい場合(つまりトークン生成の場合)、CPUレイヤーはそのままCPU上で評価されます。

  • 個人的にも、Mixtralモデルを使う場合はVRAMに詰め込むため2bitのきつい量子化を使っていたので、オフロードがしやすくなるのはとても嬉しいです。
  • 早速llama.cppを更新して、ベンチマークでPPの高速化が確認できました。ただ普段APIとして使っているkobold.cppにはまだ反映されていないので、こちらはアップデートを待ちたいと思います。

 

llama.cpp量子化:重要度行列(Importance Matrix)計算に使うテキストについて

  • 以前にも取り上げている件ですが、現在のllama.cppでは重要度行列(Importance Matrix)計算を利用することで量子化精度が改善できます。
    • 特に4bit以下の低bit量子化を行う場合は、このiMatrix版の量子化が推奨されます(Metal環境などでは推論速度が遅くなるので要注意)。
  • Claude 3 Opusによると、重要度行列とは以下のように説明できます。

Importance Matrixは、言語モデル量子化において、各パラメータの重要度を表現するために使用される行列のことを指します。

量子化の過程では、モデルの精度を維持しつつ、パラメータを低ビット幅の整数値で表現します。しかし、すべてのパラメータが同等に重要というわけではありません。そこで、Importance Matrixを用いて、各パラメータの重要度を評価します。

具体的には、以下のような手順で計算されます:

1.学習済みのモデルを用意する
2.各パラメータに対して、そのパラメータの値を微小量だけ変化させたときの、モデルの出力への影響を計算する
3.影響の大きさを正規化し、Importance Matrixの対応する要素に格納する

こうして得られたImportance Matrixは、量子化の際のビット割当てに利用されます。重要度の高いパラメータにはより多くのビットを割り当て、重要度の低いパラメータには少ないビットで済ませることで、量子化後のモデル精度の低下を最小限に抑えることができます。

Importance Matrixは、モデル圧縮の分野で広く使われている概念で、特に携帯端末やエッジデバイス向けの小型モデルを作成する際に重宝されています。同様の考え方は、pruning(枝刈り)など、他のモデル圧縮手法にも応用されています。

  • さて、llama.cppの重要度行列計算では通常、英語版のwiki.trainが入力テキストとして使われていますが、この選択が妥当なのかどうかという議論が存在します。
  • 以下はllama.cppのRepoにおけるディスカッションです。

github.com

  • 基本的には、実際の用途に近いテキストを使って重要度行列を計算するのがよいとされています。
    • 例えば、コーディング支援で使うモデルならコードを含むテキストがよく、中国語で使うモデルなら中国語を含むテキストがいいはずです。
  • 以下のデータを見ると、確かにテスト用テキストと同系統の入力テキストを利用したほうがKL-Divergenceの値が改善しています。一方で、入力テキストの量については特に精度に寄与していないことも分かります。

  • 以下は、雑多な内容の疑似ランダムテキスト、C4の英仏混合学習テキスト、通常のwiki.trainテキストを使用した場合の当惑度(Perplexity)およびLLMベンチマーク(MMLUとHellaSwag)のスコア比較です。

  • これらの結果に基づいて、llama.cppの量子化実装を手掛けているikawrakowさんは以下のように指摘しています。
    • Wikitextから得られた重要度行列は、ランダムに近いデータによる重要度行列よりも、小さいとはいえ統計的に有意に優れている。よって、基本的にWikitextを使用するのが無難である。
    • 英語以外の言語で使いたいけれど、その言語では有用なトレーニングテキストが見つからない場合は、疑似ランダムデータが使えるかもしれない。
    • フランス語のC4データセットにおけるモデルの当惑度は、フランス語だけの重要度行列を使用した場合にさらに改善する。特定言語が主なユースケースである場合、その言語だけのテキストを使用してimatrixを作成することが最善かもしれない。
  • なお、日本語テキストによる重要度行列計算については、以下の記事で簡単に検証しています。

sc-bakushu.hatenablog.com

  • 上記記事のあと、日本語モデルのKarakuri 70Bでも試してみましたが、やはり日本語のみの用途であれば日本語テキストで計算したほうが当然ながらベターな結果が得られそうです。
    • ただし、日本語のみのデータセットを使うと英語のPPLが十分に改善しなかったので、実用的には日英混合テキストのほうがよいかもしれません。