はじめに
この度、ANGEL Calendarの企画に参加しております!
記事一覧は下記のOrganizationアカウントの一覧をチェックしてみてください!
2024-ANGEL-Dojo Organization
本記事では、私が今現在個人的な興味として取り組んでいるローカルLLMについて紹介させていただこうと思います。
(Qiita初投稿のため、お見苦しい内容になっているかもしれません)
背景
昨今LLMを中心とした生成AIのサービスが次々とリリースされています。
サービス内ではOpenAIをはじめとしたAPIなどが利用されていると思います。
ただし、日本国内の生成AI導入率は19%ほどのようです。
生成AIの活用にあたり、ハードルは色々ありますが、その中には以下の意見も多いと思います。
- そもそも生成AIをどう活用してよいか分からない
- コストの計算が難しい
- セキュリティなどのコンプライアンス上、利用が制限されている
そこで、今回は上記の課題解決の一環として、自前のサーバにLLM環境を立ち上げて生成AIを利用できないかを試してみました。本記事はその検証結果を紹介しようと思います。
利用環境・ツール
1. サーバー構築
自前のローカルPCではスペックに不安があり、LLM構築としてAmazon EC2の仮想サーバーを利用します。
種類 | 内容 |
---|---|
インスタンス | t3.xlarge(vCPU:4コア Memory:16GiB) |
OS(AMI) | Ubuntu 24.04 LTS |
ストレージ(EBS) | 90BiG 汎用SSD(gp3) 3000IOPS スループット:125MiB(秒) |
その他 | Python3.12.3 |
2. Llama.cpp
ローカル環境などでLLMを動かすことができるライブラリです(cppという名前の通り、C++で実装されている)
Meta社のLLMのLlamaのモデルを動かすために実装されたようですが、GGUFという形式に変換することで
他のLLMモデルも動作させることができます。
量子化というモデルのサイズを少なくする仕組みを用いて、CPU環境でも動作するそうです。
そこで、高価なGPUを利用しないでどこまでCPUでLLMを利用できることができるのか興味が湧いたため、CPU環境で試すことにしました。
環境構築
環境構築をしてみます。
EC2ディレクトリ構成
説明するものだけを記載
~/llm # 作業ディレクトリ
├─ download.py # 利用モデルのダウンロード用Pythonスクリプト
├─.venv # Python仮想環境
└─ llama.cpp # llama.cppのカレントディレクトリ(ビルド後にできる)
├─ convert_hf_to_gguf.py # モデルのGGUF形式変換スクリプト
├─ llama-quantize # GGUF形式モデルを量子化(モデル減量化)するスクリプト
├─ llama-cli # llama.cppの
├─ llama-server # llama.cppのWebサーバ起動コマンド
├─ models
│ ├─ gemma-2-2b # 利用モデル(gemma-2-2b)の出力ディレクトリ(download.py実行後にできる)
│ │ ├─ gemma-2-2B-F16.gguf # GGUF化したモデル
│ │ ├─ gemma-2-2B-Q4_K_M.gguf # GGUFモデルを量子化したモデル
│ │ ├─ (etc:元のモデルのファイル類)
...
インストール
sudo apt update
sudo apt install make gcc build-essential
sudo apt install python3.12-venv
# Python仮想環境の作成
python3 -m venv .venv
# Python仮想環境に切り替え
source .venv/bin/activate
# Pythonで利用するモジュールのインストール
pip3 install numpy==2.1.0
pip3 install torch==2.4.0
pip3 install sentencepiece==0.2.0
pip3 install safetensors==0.4.4
pip3 install transformers==4.44.2
pip3 install huggingface_hub==0.24.6
# pip3 install llama-cpp-python
# llama.cppのGitリポジトリからモジュール一式をclone
# (cloneカレントディレクトリにllama.cppというディレクトリができる)
git clone https://github.com/ggerganov/llama.cpp
cd llama.cpp/
# lamma.cppビルド
make
これでllama.cppが利用できるようになりました!
モデルのダウンロード
試しに、googleのgemma2というモデルを利用してみました。
Hugging Faceより、ダウンロードします
ダウンロードは以下のPythonスクリプトを作って、実行します。
import huggingface_hub
model_id = "google/gemma-2-2b" # ダウンロードするモデル名
local_dir = "./llama.cpp/models/gemma-2-2b" # ダウンロードしたモデルの保存先ディレクトリ名
huggingface_hub.snapshot_download(model_id, local_dir=local_dir, token=<Hugging Faceのトークン:事前に発行>)
モデルのGGUF化→量子化
llama.cppでモデルを扱うために、GGUF形式というファイルに変換する必要があります。
GGUFは、llamma.cppディレクトリにある"convert_hf_to_gguf.py"で可能です。
また、CPUで扱うために、GGUF形式にしたモデルを、更に量子化(モデルのサイズの減量化
元のモデル → GGUF化 → 量子化
# (以後は、llama.cpp以下でコマンドを実行)
# GGUF化:パラメータには、モデルのダウンロード先のディレクトリを指定する
# 出力結果:INFO:hf-to-gguf:Model successfully exported to models/gemma-2-2b/gemma-2-2B-F16.gguf
cd ./llama.cpp
python3 convert_hf_to_gguf.py ./models/gemma-2-2b
# 量子化(オプションで指定した"Q4_K_M"で、4bit量子化ということになるようです)
./llama-quantize ./models/gemma-2-2b/gemma-2-2B-F16.gguf ./models/gemma-2-2b/gemma-2-2B-Q4_K_M.gguf Q4_K_M
量子化のオプションは、以下のNoteで分かりやすくまとめられていると思います。
https://note.com/bakushu/n/n1badaf7a91a0
これでモデルの量子化までできました(gemma-2-2B-Q4_K_M.gguf)
このモデルを使って、プロンプトを投げてみましょう。
実行
下記の通りにプロンプトを実行できます。
./llama-cli -m ./models/gemma-2-2b/gemma-2-2B-Q4_K_M.gguf -p "日本の有名な企業をいくつか教えてください"
それっぽく出てきています。(CPUであることを考えると速いです)
ただし、上の結果は比較的まともな回答をしてくれたときで、総合的にみると精度はよろしくない感じです。
同じ質問をしても、以下のように途中から回答が「株式会社三井物産」と出てくる感じで、回答が終わりません...
日本の有名な企業をいくつか教えてください。
答え:
・ 株式会社サンリオ
・ 日本航空
・ 株式会社ダイハツ工業
・ 株式会社三菱重工業
・ 株式会社東芝
・ 株式会社トヨタ自動車
・ 株式会社ソニー
・ 株式会社日清食品
・ 株式会社日本製糖
・ 株式会社三菱商事
~
・ 株式会社住友不動産
・ 株式会社三井不動産
・ 株式会社三井物産
・ 株式会社三井物産
・ 株式会社三井不動産
・ 株式会社三井物産
・ 株式会社三井物産
・ 株式会社三井物産
・ 株式会社三井物産
...(永遠と続く)
このあたりは、出力トークンを制限するオプションなどがあるようなので、それを指定するなどの対策があるかもしれません。(未検証)
WEB-API化
llama.cppは、以下のようにWEB-APIにすることもできます。
./llama-server -m ./models/gemma-2-2b/gemma-2-2B-Q4_K_M.gguf --host 0.0.0.0 --port 8080
APIの呼び出しは、curl等のHTTPクライアントで実行します。
# (指定オプション)
# stream: trueを設定することで、回答をストリーミング形式で出力
# n_predict:テキストを生成するときに予測するトークンの最大数
# その他指定可能なオプション
# https://github.com/ggerganov/llama.cpp/blob/master/examples/server/README.md
curl --request POST \
--url http://<EC2インスタンスのIPアドレス>:8080/completion \
--header "Content-Type: application/json" \
--data '{"prompt": "日本の有名な企業をいくつか教えてください", "stream": true, "n_predict": 128}'
回答の取得イメージはこちら(トークン単位で回答が取得できている)
まとめ
以上のように、自前でLLM実行環境を構築してみました。
本記事では環境構築して、プロンプトから回答が得られるところまで試してみました。
量子化で、CPUでもそれなりなパフォーマンスが発揮されましたが、回答がおかしい(ループしている?)ことも多く、実用性には疑問が残る結果となりました。
(細かいチューニングなどは何もしていないので、そのあたりもよろしくなかったと思います)
別のモデルの利用や、GPU環境にて実施することで結果は変わるかもしれないので、今後はそのアプローチも試してみようと思います。
現行でLLMを利用した生成AIアプリケーションを構築する場合、各生成AIのAPIを扱うのが現実的だと思います。一方、試してみたように自前で構築できることも分かったので、今後はセキュリティ等の要件によってはオンプレ環境のサーバーに構築するなどの選択肢も出てくるのではと感じました。
補足
自前のLLM環境構築に、Ollamaというツールもあるようです
(内部でllama.cppが使われており、扱いも簡単のようです。こちらはまたの機会に...)
参考リンク
この記事は、その他、以下の情報を参考にさせていただきました。