GGUF型式への変換でメモリ不足になる場合の回避策


# メモリ不足でフリーズや強制ログアウトなどになるのを、回避します


https://note.com/schroneko/n/n321b340e3d03


上記をみて、試してみました。でもメモリは192Gも私の環境にはありません。たったの4Gです😂。
古めのノートPCです。  

そんな少ないメモリのノートPCのManjaro Linux上で、同じ手順を実行しても、メモリ不足でいろんなプロセスが強制終了されていきます。  
そして最終的にはログアウトやフリーズなどになります。

そんなメモリが少ない環境でも、時間は10倍くらいの50分弱かかりましたが、`add_tensor`に`--use-temp-file`オプションを渡すことで、変換自体は可能でした。
なお`run_inference.py`は、反応がかえってくるのに、数分は待つ必要があります。もちろんその間は、他の処理を行うことは難しく、他の操作はままなりません。
ただ時間はかかりますが、のんびり待機していれば、どんな反応が返ってくるかを試すこと自体はできました。

## ポイント
1. gguf_writer.pyのadd_tensorに`--use-temp-file`オプションの指定が渡されるようにする。
2. TMPDIR(環境によってはTMP,TEMP)を指定して実行する

/tmpは最近の多くのLinuxの場合、メモリ上に展開されています。私の環境では4Gのメモリのうち、1.7G確保されていました。
swapパーティションを相当大きめに設定していたとしても、巨大なファイルの操作などを行うとメモリ不足になりそうです。
もちろん/tmpにストレージをマウントして割り当てることも可能です。途中からそういう設定に切り替えることも可能です。その場合、tmpfile.dなどの設定も適切に行えば、パーミッションや削除のタイミングなども制御できるかと思います。[^1]

実際に、試した手順は下記のとおりです。

# Python仮想環境の構築
## uvのインストール
uvをpythonの仮想環境構築に用いる場合、pythonのバージョンも指定できることが利点です。
古めのLinuxでも、新しいPython環境での動作が確認できますね。
参考記事にあわせてuvを用いて、Manjaroで環境構築を行ってみます。

uvのインストール方法はいろいろあります。uvパッケージがありますので、そちらを素直にインストール。
```sh
sudo pacman -S uv
```
パッケージがない場合の一例

https://docs.astral.sh/uv/getting-started/installation/#standalone-installer

```sh
curl -LsSf https://astral.sh/uv/install.sh | sh
```

cargo binstallを導入している場合。
```sh
cargo bintall --git https://github.com/astral-sh/uv uv
```
ビルドしてインストールする場合
```sh
sudo pacman -S rustup
cargo intall --git https://github.com/astral-sh/uv uv
```

## pixi
pixiという、パッケージマネージャもあるそうです。  
PyPIについては、内部でuvを使っているそうです。condaをお使いの方にも、良いらしいです。  
紹介記事のリンクだけ、追加しておきます。

https://zenn.dev/yahooshiken/articles/getting-started-ros2-with-pixish


# BitNetレポジトリを複製する
試したいBitNetのレポジトリの複製です。
```sh
git clone --recursive https://github.com/microsoft/BitNet.git
cd BitNet
```

# 関連モジュールのインストール
BitNetフォルダにPython仮想環境を構築して、関連モジュールのインストールを行います。
```sh
uv venv --python 3.12
source .venv/bin/activate
uv pip install --index-strategy unsafe-best-match -r requirements.txt
uv pip install pip
uv pip list
```

# setup_env.pyの修正
`utils/convert-hf-to-gguf-bitnet.py`の箇所には、オプションとして、`--use-temp-file`を追加しておきます。これによりメモリ不足で実行が停止するのを回避します。
```diff
diff --git a/setup_env.py b/setup_env.py
index b9bf5fc..8b829a2 100644
--- a/setup_env.py
+++ b/setup_env.py
@@ -92,10 +92,10 @@ def prepare_model():
     if not os.path.exists(gguf_path) or os.path.getsize(gguf_path) == 0:
         logging.info(f"Converting HF model to GGUF format...")
         if quant_type.startswith("tl"):
-            run_command([sys.executable, "utils/convert-hf-to-gguf-bitnet.py", model_dir, "--outtype", quant_type, "--quant-embd"], log_step="convert_to_tl")
+            run_command([sys.executable, "utils/convert-hf-to-gguf-bitnet.py", model_dir, "--outtype", quant_type, "--quant-embd", "--use-temp-file"], log_step="convert_to_tl")
         else: # i2s
             # convert to f32
-            run_command([sys.executable, "utils/convert-hf-to-gguf-bitnet.py", model_dir, "--outtype", "f32"], log_step="convert_to_f32_gguf")
+            run_command([sys.executable, "utils/convert-hf-to-gguf-bitnet.py", model_dir, "--outtype", "f32", "--use-temp-file"], log_step="convert_to_f32_gguf")
             f32_model = os.path.join(model_dir, "ggml-model-f32.gguf")
             i2s_model = os.path.join(model_dir, "ggml-model-i2_s.gguf")
             # quantize to i2s
```

# カレントディレクトリにTMPDIR用のディレクトリを作成する
```sh
mkdir -p .tmp
```

# OOM_KILLERの対象外にする
負荷の高い処理のため、シェルでの操作を強制終了の対象外にしておきます。

bash,zshなどの場合
```sh
echo $$|xargs -n1 sudo choom -n -1000 -p
```
fish shellの場合
```sh
echo $fish_pid|xargs -n1 sudo choom -n -1000 -p
```

# GGUF型式への変換
メモリ不足回避のために、TMPDIR(環境によってはTMP,TEMP)の指定を忘れないようにしましょう。($PWDはカレントディレクトリの絶対パスです。)  
私の環境で50分弱かかりました。
```sh
TMPDIR=$PWD/.tmp python setup_env.py --hf-repo HF1BitLLM/Llama3-8B-1.58-100B-tokens -q i2_s
```

# run_inference.pyの試行
実行すると、数分から10数分、待機しましょう。他の処理はできないと思ってください。
```sh
python run_inference.py -m models/Llama3-8B-1.58-100B-tokens/ggml-model-i2_s.gguf -p "Daniel went back to the the the garden. Mary travelled to the kitchen. Sandra journeyed to the kitchen. Sandra went to the hallway. John went to the bedroom. Mary went back to the garden. Where is Mary?\nAnswer:" -n 6 -temp 0
```

# まとめ
1. `--use-temp-file`オプションを用いて、メモリ不足の回避を行う。
```sh
grep use-temp-file -r 3rdparty/ -A1
3rdparty/llama.cpp/convert_hf_to_gguf.py:        "--use-temp-file", action="store_true",
3rdparty/llama.cpp/convert_hf_to_gguf.py-        help="use the tempfile library while processing (helpful when running out of memory, process killed)",
```

2. TMPDIRの指定
一時ファイルとして最低でも2Gバイトのファイルを確保しようとします。また今回の例のファイルの変換だと、書き出しには一時的に30数Gの容量が必要そうです。  
そのためメモリ上に展開している/tmpフォルダでは、容量が足りない場合も多いでしょう。  
TMPDIR(環境によってはTEMP,TMP)として、余裕のあるストレージのフォルダを指定することで、巨大な一時ファイルの書き出しを可能にします。

この2点を留意すれば、時間はものすごくかかりますが、いろんな環境で実行自体は可能だと思います。

[^1]: [/tmpをサブボリューム@tmpのマウントへ変更する - Qiita](https://qiita.com/phoepsilonix/items/7cf15d47e02e44efb63e)