小規模なLLMのMerge(圧縮・強化)、GGUF化と量子化、Ollamaの使い方の注意点

　本記事はパラメータが3B（30億パラメータ）以下の比較的小規模なLLMのマージや量子化の効率的な処理方法と、それをローカルで動かす際の、Ollamaの使い方の注意点についてまとめたものです。実際に実行した環境は以下の通りです。
　・ローカルPC：　M1 Macbook Air Ventura13.6.7 メモリ８GB CPU８コア・GPU７コア

1. モデルのマージ
　比較的簡単にモデルのマージをしたい場合には、定番のMergekitがお勧めですが、更にインストールや各種設定等の手間も省きたい人にお勧めなのはLazyMergekitです。
    [　リンク：LazyMergekitのnotebook(Github: mlabonne/llm-course）　](https://github.com/mlabonne/llm-course/tree/main)   
    
 このGithubサイトにあるLazyMergekitのnotebookリンクを開き、自分のGoogleDriveにコピーして使います。最初マージに必要な設定情報を下記の例のようにyamlファイルとして設定すれば、あとはHuggingFace（以下HF）へのアップロード（予めHFの登録とトークンの設定が必要）までほぼ自動で処理してくれます。また、Colabの有償版（Pro等のGPU使用）も使えますが、3B以下のモデルのマージであれば無料版で処理可能だと思います。
```slerpマージの設定例.
　MODEL_NAME = "NeuralPipe-7B-slerp"
yaml_config = """
slices:
  - sources:
      - model: OpenPipe/mistral-ft-optimized-1218
        layer_range: [0, 32]
      - model: mlabonne/NeuralHermes-2.5-Mistral-7B
        layer_range: [0, 32]
merge_method: slerp
base_model: OpenPipe/mistral-ft-optimized-1218
parameters:
  t:
    - filter: self_attn
      value: [0, 0.5, 0.3, 0.7, 1]
    - filter: mlp
      value: [1, 0.5, 0.7, 0.3, 0]
    - value: 0.5
dtype: bfloat16
"""   
```
　マージ手法はmergekitが対応している、linear、slerp、dare_ties、passthrough、model_stock、task_arithmetic、そしてMoEも使えますが、MoEの場合のみbranchをmainからmixtralに変更する必要があります。
　マージ手法の中でお勧めはslerpとdare_ties、そしてMoEです。各手法のポイントは以下の通りです。なお、モデルアーキテクチャ、レイヤ数、vocab_sizeは、HF上の各モデルの config.json ファイルで確認できます。
 ・slerp:　同じモデルアーキテクチャでかつ同じレイヤ数を持つ2つのモデルをマージ。
 ・dare_ties：　同じモデルアーキテクチャでかつ同じレイヤ数を持つ複数（3つ以上も可能）のモデルをマージ。vocab_sizeの同じモデル同士で無いとエラーで中断される場合がある。
 ・MoE：　Mixture of Expertsで複数のモデルを専門家（Experts）としてポジティブプロンプトを指定してマージ。モデルの強化に役立つ手法ですが、マージで出来上がる新モデルのサイズはほぼ足し算？になるので、ファイルサイズが肥大化するデメリットもあります。
 　参考に成功？したdare_tiesとMoEの設定例を載せておきます。
 ```dare_tiesマージの設定例.
　MODEL_NAME = "NeuralPipe-7B-slerp"
yaml_config = """
slices:
- sources:
  - layer_range: [0, 24]
    model: rinna/japanese-gpt-neox-3.6b
    parameters:
      density: [1, 0.7, 0.1]
      weight: 1.0
  - layer_range: [0, 24]
    model: rinna/japanese-gpt-neox-3.6b-instruction-sft-v2
    parameters:
      density: 0.33
      weight:
        - filter: mlp
          value: 0.5
        - value: 0
merge_method: dare_ties
base_model: rinna/japanese-gpt-neox-3.6b
parameters:
  normalize: true
  int8_mask: true
dtype: bfloat16
"""   
```
```MoEマージの設定例.
　MODEL_NAME = "NeuralPipe-7B-slerp"
yaml_config = """
base_model: vihangd/DopeyTinyLlama-1.1B-v1
experts:
  - source_model: vihangd/DopeyTinyLlama-1.1B-v1
    positive_prompts:
    - "chat"
    - "assistant"
    - "tell me"
    - "explain"
  - source_model: Tensoic/TinyLlama-1.1B-3T-openhermes
    positive_prompts:
    - "reason"
    - "provide"
    - "instruct"
    - "summarize"
    - "count"
"""   
```
　実は同一のモデル同士をslerpで複数回マージすると日本語能力が向上したりという、裏技的な手法もあるので実に奥が深いですね。私もまだまだ発展途上です。
　また、Prune（レイヤを剪定してモデルを圧縮する手法）に使われるのが、passthroughという単に指定したレイヤのtensorを受け渡すマージ手法で、別名フランケン手法とも呼ばれています。Pruneの場合は実際には下記の例のように、同一モデル同士でレイヤ数を減らしてマージします（Llama3-70Bのレイヤ数は80なので、レイヤ30~50とレイヤ70~80が剪定される）。
 MergekitのGithubを運営するarcee-aiでは、[PruneMe](https://github.com/arcee-ai/PruneMe)というPruneで剪定すべきレイヤ位置を特定する自動化ツールを提供しており、Colab等で実際に使うことが出来るので、興味のある方は試してみて下さい。

```passthroughのPrune設定例.
　MODEL_NAME = "mergekit-passthrough-yqhuxcv"
yaml_config = """
 slices:
- sources:
  - layer_range: [0, 20]
    model: aaditya/Llama3-OpenBioLLM-70B
- sources:
  - layer_range: [50, 70]
    model: aaditya/Llama3-OpenBioLLM-70B
- sources:
  - layer_range: [20, 30]
    model: aaditya/Llama3-OpenBioLLM-70B
merge_method: passthrough
dtype: float16
"""
```
　上記の例はHFの[MergeKit Community](https://huggingface.co/mergekit-community)からの引用ですが、ここでは様々なマージ方法が紹介されているので、参考になると思います。また、同じHF上に[open-llm-leaderboard](https://huggingface.co/spaces/open-llm-leaderboard/open_llm_leaderboard)というサイトがあり、ここでも高評価を受けたマージモデルが多く紹介されています。
　日本のsakana.aiさんの[「進化的アルゴリズムによる基盤モデルの構築」](https://sakana.ai/evolutionary-model-merge-jp/)の論文の影響を受けて、MergekitではEvolveマージにも対応したようです。興味のある方は下記リンクを参照して下さい。
 [リンク：mergekit-evolve](https://github.com/arcee-ai/mergekit/blob/107008ead4ae3ff3aa0671bcc79feda20807f139/docs/evolve.md#L4)
　※ PruneMeとmergekit-evolveはGPUが必須になるので、Colab無償版ではリソースネックになる確率が高いです。
  
 
２.GGUF化・量子化
　自分でマージしたモデルをローカルLLMとして検証するためには、Ollama用にGGUF化と量子化が必要になります。上記で紹介したmlabonne/llm-courseのGithubサイトには量子化のnotebookリンクもあるのでこれを利用するか、llama.cppを自分でインストールして、その中のconvert.pyか、convert-hf-to-gguf.pyを利用してGGUF化し、quantizeを使って量子化します。
 モデルの量子化については、多くの日本語対応モデルを実際にHFにアップロードされている[mmngaさんのサイト](https://huggingface.co/mmnga)がとても参考になりますが、自分でマージしたモデルは自分で量子化するしか無いので、以下にやり方を簡単にご紹介します。
　① 先ずは量子化したいモデルをHFからのダウンロード等により用意してパスを確認しておきます。この際git cloneを使い大容量ファイルをダウンロードするので、[git LFSをインストール](https://docs.github.com/ja/repositories/working-with-files/managing-large-files/installing-git-large-file-storage)しておきます。
 ```GGUF化.
 git clone <モデルのURL>
 cd <convert.py等が存在するllama.cppのパス>
 python3 convert-hf-to-gguf.py <クローンしたモデルのパス>/<クローンしたモデル名>
 #上記で失敗する場合は下記も試してみる
 python3 convert.py <クローンしたモデルのパス>/<クローンしたモデル名>
 ```
 　② GGUF化が上手く行ったら<クローンしたモデルのパス>に、<モデル名>-1B-F16.ggufか、<モデル名>-1B-F32.ggufというファイルが生成されます。次はこの生成されたGGUFファイルを量子化します。
```量子化.
./quantize <GGUFファイルのパス>/<GGUFファイル名> <量子化ファイルの保存先パス>/<量子化ファイル名> <量子化の種別を指定>
#4bit(Q4_K_M)量子化の実際の例
./quantize ./Models/mergedmodel1/mergedmodel-1B-F16.gguf ./Models/mergedmodel1/ggml-model-Q4_K_M.gguf Q4_K_M
```
　以上でggml-model-Q4_K_M.ggufという量子化されたモデルファイルが生成されますので、これをOllamaのModelfileに設定して、ollama createして使って下さい。
 　実際にやってみると特にGGUF化は色々エラーで躓く事が多いので、幾つかの役に立ちそうな対処法を記載しておきます。
  ・convert-hf-to-gguf.pyでダメな場合は、convert.pyでも試してみると上手くいく場合があります。また、convert-hf-to-gguf.py等はllama.cppの中でもアップデート頻度が高い（数日？）ので、この2つのファイルのみアップデートして解決される場合もあります。
  ・上記で紹介したmmngaさんが特殊なモデルの変換スクリプトを用意してくれている場合がありますので、行き詰まったら確認してみて下さい（私も何度か助けてもらいました）。
  ・vocab sizeに端数が加算されている場合（Mergekitで最後にvocabが新たに追加されることがある）にGGUF化でエラーが出ますが、convert.pyのオプションに --pad-vocabを付加すれば変換できる場合があります。
　なお量子化まで成功しても、Ollamaの起動に失敗するケースもあり、この場合Ollamaが未対応のモデルアーキテクチャである可能性が高いです。

３. Ollamaの使い方の注意点
　量子化の所でも触れましたが、Ollamaが対応していないモデルは起動できない場合があります。詳細は分かりませんが、rinnaさんのjapanese-gpt-neox-3.6bモデルのような、モデルアーキテクチャが"GPTNeoXForCausalLM"のものは起動出来ませんでした。Ollamaもバックではllama.cppを使っているようですが最新バージョンでない可能性が高く、本家のllama.cppではgpt-neoxに対応済みで動かせました。Ollamaが対応している[モデル一覧](https://ollama.com/library)は公式サイトに掲載されていますが、これはModelfileを使わずに直接HFからpullしたりrunしたりできるモデルのことで、Modelfileを使えば公式のモデル一覧に無いモデルでも、llama.cppが対応していればOllamaでも使えると思っていました。概ね正しいようですが、gpt-neoxのように一部のモデルは、llama.cppのバージョンの違いで使えない場合があるようです。
　【対処策】　MoEのマージでできるモデルのアーキテクチャは、"MixtralForCausalLM"に変換されるので、どうしてもOllamaで扱いたい場合は同一モデルによるMoEマージで、アーキテクチャを変更する。
 
　また、現時点OllamaはiMatrixの量子化（IQ3_XXSなど）に対応していません。llama.cppはiMatrixの量子化に対応しており、量子化自体もできます。これはOllama側の対応を待つしか無いようです（現在取組中のようです）。

４. まとめ
　今回の記事では（小規模）LLMの独自のマージモデルの簡単な作り方、作ったマージモデルのGGUF化・量子化のやり方、そしてそれを用いてOllamaでモデルを利用する際の注意点をまとめてみました。この一連の処理を行えば、PruneやEvolve以外はColabの無料枠内とローカル環境で全て処理出来て、独自のマージモデルの量産?も可能になりますので、是非試してみて下さい。また、内容が古かったり、間違っている場合はご指摘頂けると嬉しいです。