10
5

ローカルLLMを構築してみた(llma.cpp)

Posted at

はじめに

この度、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:元のモデルのファイル類)
    ...

インストール

ubuntuのモジュールをインストール
sudo apt update 
sudo apt install make gcc build-essential
sudo apt install python3.12-venv
Python環境の整備
# 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のインストール
# 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スクリプトを作って、実行します。

download.py
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 "日本の有名な企業をいくつか教えてください"

answer-image.gif

それっぽく出てきています。(CPUであることを考えると速いです)

ただし、上の結果は比較的まともな回答をしてくれたときで、総合的にみると精度はよろしくない感じです。

同じ質問をしても、以下のように途中から回答が「株式会社三井物産」と出てくる感じで、回答が終わりません...

日本の有名な企業をいくつか教えてください。

答え:

・ 株式会社サンリオ
・ 日本航空
・ 株式会社ダイハツ工業
・ 株式会社三菱重工業
・ 株式会社東芝
・ 株式会社トヨタ自動車
・ 株式会社ソニー
・ 株式会社日清食品
・ 株式会社日本製糖
・ 株式会社三菱商事
~
・ 株式会社住友不動産
・ 株式会社三井不動産
・ 株式会社三井物産
・ 株式会社三井物産
・ 株式会社三井不動産
・ 株式会社三井物産
・ 株式会社三井物産
・ 株式会社三井物産
・ 株式会社三井物産
...(永遠と続く)

このあたりは、出力トークンを制限するオプションなどがあるようなので、それを指定するなどの対策があるかもしれません。(未検証)

WEB-API化

llama.cppは、以下のようにWEB-APIにすることもできます。

WEB-API(ポート8080)として実行
./llama-server -m ./models/gemma-2-2b/gemma-2-2B-Q4_K_M.gguf --host 0.0.0.0 --port 8080

APIの呼び出しは、curl等のHTTPクライアントで実行します。

APIで回答を生成
# (指定オプション)
# 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}'

回答の取得イメージはこちら(トークン単位で回答が取得できている)
answer-image_api.gif

まとめ

以上のように、自前でLLM実行環境を構築してみました。
本記事では環境構築して、プロンプトから回答が得られるところまで試してみました。
量子化で、CPUでもそれなりなパフォーマンスが発揮されましたが、回答がおかしい(ループしている?)ことも多く、実用性には疑問が残る結果となりました。
(細かいチューニングなどは何もしていないので、そのあたりもよろしくなかったと思います)

別のモデルの利用や、GPU環境にて実施することで結果は変わるかもしれないので、今後はそのアプローチも試してみようと思います。

現行でLLMを利用した生成AIアプリケーションを構築する場合、各生成AIのAPIを扱うのが現実的だと思います。一方、試してみたように自前で構築できることも分かったので、今後はセキュリティ等の要件によってはオンプレ環境のサーバーに構築するなどの選択肢も出てくるのではと感じました。

補足

自前のLLM環境構築に、Ollamaというツールもあるようです
(内部でllama.cppが使われており、扱いも簡単のようです。こちらはまたの機会に...)

参考リンク

この記事は、その他、以下の情報を参考にさせていただきました。

10
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
5