ローカルLLM(プライベートLLM)

本記事は日本オラクルが運営する下記Meetupで発表予定の内容になります。発表までに今後、内容は予告なく変更される可能性があることをあらかじめご了承ください。

https://oracle-code-tokyo-dev.connpass.com/event/325752/

※本記事は下記記事の補足記事となります。

https://qiita.com/ksonoda/items/f3c703fb7b689fd65868


※ミートアップ実施時の動画は↓こちら。

<iframe width="560" height="315" src="https://www.youtube.com/embed/XQCKTrnxO88?si=o7FeZB6h_no65YbO" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

# はじめに
ChatGPTのリリース以降、大規模言語モデルの有用性に注目があつまり、LLMのクラウドサービスを利用することが当たり前になっている現在ですが、それと同時に、クラウドサービスではなくローカルでLLMを運用するアーキテクチャも現実的になってきています。(※呼称としてはローカルLLMやプライベートLLMと言ったりします。)

その背景には以下のような理由が挙げられると思います。

- 様々な団体から実用に耐える高性能なOSSのLLMが多数リリースされている
- それを動作させるハードウェア(特にGPU)性能の著しい技術発展
- セキュリティやプライバシーに関するシステム要件のプライオリティがますます高くなっている

言語モデルのクラウドサービスが存在しなかった時代はみんな苦労しながらローカルでモデルを動作させていましたので特段新しい考え方ではありませんが、昨今では様々なツールがリリースされており、動作させるだけであれば以前のような高度な知識は全く不要になっているというのが実情です。




# ローカルLLMの仕組みとPros/Cons
LLMをローカルで動作させるアーキテクチャではほとんどの処理をOSSのツールに頼ることになります。これには様々な手法がありますが本記事ではその中の代表的な方法としてOllamaと呼んでいるOSSを使うパターンとHugging Faceを利用する2パターンをご紹介します。

下図がOllamaを使ったローカルLLMの実装です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109260/b8727430-244c-4030-bd19-1419b0d21ba1.png)

- 一般的なLLMのクラウドサービスの場合(上図左)
例えばOpenAIを使う場合は、クラウド、もしくはオンプレミスにLLMを利用するアプリケーションを実装し、そのアプリからインターネット(もしくは専用線やVPN)経由でOpenAI社のデータセンターに実装されているモデルを利用(APIでCall)します。OpenAIのモデルをダウンロードしてローカルで動作させることは当然できません。

- ローカルLLMの実装(上図右 Ollamaを使った場合)
モデルそのものをローカル環境にダウンロードし、ローカル環境にデプロイします。アプリケーションからのAPI Callもローカル環境内で全て完結するということになります。(Ollamaがサポートしているモデルの一覧は[こちら](https://ollama.com/library))


そして下図がHugging Faceを利用したローカルLLMの実装です。今やAIテクノロジー界隈では説明の必要がないほど重要なプラットフォームになった[Hugging Face](https://huggingface.co/)を利用するパターンです。現在市場でリリースされているほぼすべてのOSSモデルが登録されおり、githubのLLM版と称されるほど知名度の高いサイトです。(Hugginf Faceで利用できるモデルの一覧は[こちら](https://huggingface.co/models)。)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109260/e16c7a8b-4563-d02c-f793-e58645f36b3a.png)

ローカルLLMですのでモデルをリポジトリからローカルにダウンロードするという考え方はOllamaと変わりません。Higgin Faceのモデルを利用する場合、そのモデルを開発した企業や団体がモデル利用のAPIを提供し、Higgin Faceのサイトで公開していますのでそれに倣って実装することになります。このAPIは各社が開発しているということもあり、LLMによって微妙に方言がありコーディングが異なります。そこで、LangChainでは各社のAPIをラップしたHuggingFacePipelineというクラスが用意されており、このクラスでどのモデルもほぼ同じお決まりのコードで実装することができるようになります。もちろん抽象度の高い関数が用意されていますのでコーディングも簡素化されます。


ローカルLLMを使うアーキテクチャのpros/consとしては下記の通りです。


| メリット                          | 説明                                                                                     |
|-----------------------------------|------------------------------------------------------------------------------------------|
| プライバシーとセキュリティ         | データがローカルに保持されるため、機密情報を外部サーバーに送信する必要がなくなり、プライバシーとセキュリティが向上します。 |
| 様々なモデルが利用可能         | 様々な団体・企業が公開しているモデルを利用可能です。特定のタスクに特化したモデルなども簡単に利用することができます。 |
| カスタマイズの自由度              | モデルの調整や微調整を容易に行えるため、特定のユースケースに最適化されたモデルを作成できます。               |
| インターネット接続が不要           | オフライン環境でもLLMを使用できるため、ネットワークに依存しない運用が可能です。                                     |
| コスト削減                        | クラウドベースのサービスと比べ、長期的に見てコストを削減できる場合があります（ただし、初期投資は必要）。                  |
| レイテンシの低減                  | ローカル環境での処理により、クラウド経由の通信による遅延がなくなり、リアルタイム処理が向上します。                    |

| デメリット                        | 説明                                                                                     |
|-----------------------------------|------------------------------------------------------------------------------------------|
| ハードウェア要件               | LLMをローカルで動作させるには、非常に高性能なハードウェアが必要であり、特にGPUや大量のメモリが必要になります。 |
| 運用と管理の負担                  | モデルのインストール、更新、メンテナンス、トラブルシューティングなどを自分で行う必要があり、運用コストや時間がかかります。 |
| 拡張性の制限                      | ローカル環境では、スケーリングが難しく、クラウドのように簡単にリソースを追加できません。                              |
| 初期コストの高さ                  | 必要なハードウェアを購入するための初期投資が大きく、これが特に小規模なプロジェクトにとっては負担になることがあります。       |
| 更新とサポートの遅延              | クラウドサービスは最新のモデルや機能が即座に利用可能ですが、ローカル環境ではこれらを手動で更新する必要があります。         |


最低でもこの程度のpros/consは念頭にアーキテクチャを採用を決める感じです。ローカルLLMを導入するユーザーはやはりセキュリティとコスト削減のメリットを重視した結果、ローカルLLMを選択するという結論に至るケースがほとんどです。

# ユースケース
全てのLLMアプリケーションで利用可能です。特に上述したメリットが重要な要件になるシステムで検討するべき構成となります。他の記事で紹介している実装パターンは基本的にローカルLLMでも実装可能です。




# 実装の概説(Ollamaのパターン)
Ollamaを使った実装を概説します。ステップとしては大きく下記3ステップです。

①ローカルにOllamaをインストール
②目的のモデルをローカルにダウンロードし、デプロイ
③推論(テキスト生成)の実行


まずは「①ollamaのダウンロード、インストール」です。

```bash
$ curl -fsSL https://ollama.com/install.sh | sh
########################################################################################################################################################################################################################################################################################################################################################################################################################################################################## 100.0%
>>> Making ollama accessible in the PATH in /usr/local/bin
>>> Creating ollama user...
>>> Adding ollama user to video group...
>>> Adding current user to ollama group...
>>> Creating ollama systemd service...
Failed to get D-Bus connection: Operation not permitted
WARNING: Unable to detect NVIDIA/AMD GPU. Install lspci or lshw to automatically detect and install GPU dependencies.
>>> The Ollama API is now available at 127.0.0.1:11434.
>>> Install complete. Run "ollama" from the command line.
```

インストールしたollamaを起動します。

```bash
$ ollama serve
Couldn't find '/home/datascience/.ollama/id_ed25519'. Generating new private key.
Your new public key is: 

ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKLykZoGs0m3PQ/7d1h8/rkkNuow0t2viTibg6zgKXZL

2024/08/22 11:18:09 routes.go:1125: INFO server config env="map[CUDA_VISIBLE_DEVICES: GPU_DEVICE_ORDINAL: HIP_VISIBLE_DEVICES: HSA_OVERRIDE_GFX_VERSION: OLLAMA_DEBUG:false OLLAMA_FLASH_ATTENTION:false OLLAMA_HOST:http://127.0.0.1:11434 OLLAMA_INTEL_GPU:false OLLAMA_KEEP_ALIVE:5m0s OLLAMA_LLM_LIBRARY: OLLAMA_MAX_LOADED_MODELS:0 OLLAMA_MAX_QUEUE:512 OLLAMA_MODELS:/home/datascience/.ollama/models OLLAMA_NOHISTORY:false OLLAMA_NOPRUNE:false OLLAMA_NUM_PARALLEL:0 OLLAMA_ORIGINS:[http://localhost https://localhost http://localhost:* https://localhost:* http://127.0.0.1 https://127.0.0.1 http://127.0.0.1:* https://127.0.0.1:* http://0.0.0.0 https://0.0.0.0 http://0.0.0.0:* https://0.0.0.0:* app://* file://* tauri://*] OLLAMA_RUNNERS_DIR: OLLAMA_SCHED_SPREAD:false OLLAMA_TMPDIR: ROCR_VISIBLE_DEVICES:]"
time=2024-08-22T11:18:09.238Z level=INFO source=images.go:782 msg="total blobs: 0"
time=2024-08-22T11:18:09.238Z level=INFO source=images.go:790 msg="total unused blobs removed: 0"
time=2024-08-22T11:18:09.238Z level=INFO source=routes.go:1172 msg="Listening on 127.0.0.1:11434 (version 0.3.6)"
time=2024-08-22T11:18:09.239Z level=INFO source=payload.go:30 msg="extracting embedded files" dir=/tmp/ollama2907998899/runners
time=2024-08-22T11:18:13.008Z level=INFO source=payload.go:44 msg="Dynamic LLM libraries [rocm_v60102 cpu cpu_avx cpu_avx2 cuda_v11]"
time=2024-08-22T11:18:13.008Z level=INFO source=gpu.go:204 msg="looking for compatible GPUs"
time=2024-08-22T11:18:13.445Z level=INFO source=types.go:105 msg="inference compute" id=GPU-009ad1f3-4be6-f778-4299-d0af3aa15302 library=cuda compute=8.6 driver=12.4 name="NVIDIA A10" total="22.0 GiB" available="15.1 GiB"
time=2024-08-22T11:18:13.445Z level=INFO source=types.go:105 msg="inference compute" id=GPU-90718f7c-35b9-a8c9-7ca4-08cb52abbff0 library=cuda compute=8.6 driver=12.4 name="NVIDIA A10" total="22.0 GiB" available="13.6 GiB"
```

(※このターミナルには以降の処理実行時にログが表示されますのでこのまま残し、以降の処理は別のターミナルで実行します。)

下記コマンドでデプロイされているモデルが確認できます。現時点ではモデルをデプロイする前なので何も表示されません。

```bash
$ ollama list
NAME    ID      SIZE    MODIFIED 
```

次に「②モデルをダウンロードし、デプロイ」します。下記のようにollama pullコマンドで目的のモデル名をしていするだけです。今回は[cohere社](https://cohere.com/)のcommand-r-plusというモデルを使ってみます。OpenAIと肩を並べる高性能のテキスト生成モデルで日本語にも対応しています。(Ollamaがサポートしているモデルの一覧は[こちら](https://ollama.com/library))

因みに、環境にも依存すると思いますが下記処理で約10分ほどかかりました。

```bash
$ ollama pull command-r-plus
pulling manifest 
pulling 503c8cac166f... 100% ▕█████████████████████████████████████████████████▏  59 GB                         
pulling f0624a2393a5... 100% ▕█████████████████████████████████████████████████▏  13 KB                         
pulling c90176f4de00... 100% ▕█████████████████████████████████████████████████▏ 2.9 KB                         
pulling 36b9655abe6a... 100% ▕█████████████████████████████████████████████████▏   81 B                         
pulling 748dd5320e31... 100% ▕█████████████████████████████████████████████████▏  493 B                         
verifying sha256 digest 
writing manifest 
removing any unused layers 
success 
```

再度、デプロイされているモデルを確認すると、以下のようにcommand-r-plusのエントリが確認できます。

```bash
$ ollama list
NAME                    ID              SIZE    MODIFIED       
command-r-plus:latest   777d41fdcb0e    59 GB   12 minutes ago
```



この環境はNvidia A10 GPUが2枚搭載されているインスタンスです。GPUのメモリがどれくらい消費されているかを確認すると、モデルをロードするだけで約15GB強(6834MiBと8362MiBの合計)のメモリを消費していることがわかります。

```bash
$ nvidia-smi
Thu Aug 22 11:48:55 2024       
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 550.90.07              Driver Version: 550.90.07      CUDA Version: 12.4     |
|-----------------------------------------+------------------------+----------------------+
| GPU  Name                 Persistence-M | Bus-Id          Disp.A | Volatile Uncorr. ECC |
| Fan  Temp   Perf          Pwr:Usage/Cap |           Memory-Usage | GPU-Util  Compute M. |
|                                         |                        |               MIG M. |
|=========================================+========================+======================|
|   0  NVIDIA A10                     Off |   00000000:00:04.0 Off |                    0 |
|  0%   44C    P0             59W /  150W |    6834MiB /  23028MiB |      0%      Default |
|                                         |                        |                  N/A |
+-----------------------------------------+------------------------+----------------------+
|   1  NVIDIA A10                     Off |   00000000:00:05.0 Off |                    0 |
|  0%   42C    P0             57W /  150W |    8362MiB /  23028MiB |      0%      Default |
|                                         |                        |                  N/A |
+-----------------------------------------+------------------------+----------------------+
                                                                                         
+-----------------------------------------------------------------------------------------+
| Processes:                                                                              |
|  GPU   GI   CI        PID   Type   Process name                              GPU Memory |
|        ID   ID                                                               Usage      |
|=========================================================================================|
+-----------------------------------------------------------------------------------------+
```

次は最後のステップ「③推論の実行」です。下記コマンドで推論を行います。

```bash
$ ollama run command-r-plus "日本の首相は誰ですか？"
```

出力は以下の通り。ローカルで動作させていますのでレスポンスはとにかく早いです。

```bash
岸田文雄が2021年9月29日から日本第100代内閣総理大臣を務めています。彼は任期中にコロナ禍への対応、防衛政策の強化、「新しい資本主義」と呼ばれる経済政策の実装などを行ってきました。自民党の総裁でもあります
```

推論中のGPUメモリの消費量は当然一気に跳ね上がり、約40GB強。

```bash
$ nvidia-smi
Thu Aug 22 11:54:43 2024       
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 550.90.07              Driver Version: 550.90.07      CUDA Version: 12.4     |
|-----------------------------------------+------------------------+----------------------+
| GPU  Name                 Persistence-M | Bus-Id          Disp.A | Volatile Uncorr. ECC |
| Fan  Temp   Perf          Pwr:Usage/Cap |           Memory-Usage | GPU-Util  Compute M. |
|                                         |                        |               MIG M. |
|=========================================+========================+======================|
|   0  NVIDIA A10                     Off |   00000000:00:04.0 Off |                    0 |
|  0%   45C    P0             63W /  150W |   21485MiB /  23028MiB |     12%      Default |
|                                         |                        |                  N/A |
+-----------------------------------------+------------------------+----------------------+
|   1  NVIDIA A10                     Off |   00000000:00:05.0 Off |                    0 |
|  0%   43C    P0             58W /  150W |   18785MiB /  23028MiB |      8%      Default |
|                                         |                        |                  N/A |
+-----------------------------------------+------------------------+----------------------+
                                                                                         
+-----------------------------------------------------------------------------------------+
| Processes:                                                                              |
|  GPU   GI   CI        PID   Type   Process name                              GPU Memory |
|        ID   ID                                                               Usage      |
|=========================================================================================|
+-----------------------------------------------------------------------------------------+
```


もちろんノートブックからPythonなどで推論処理することもできます。その場合もlangchainを使うと便利です。langchainのcommunityパッケージには当然ollamaもインテーグレートされており、下記のように数ステップで推論処理を実行できます。

langchain-communityのインストール

```python
!pip install -Uq langchain-community
```

llmを定義

```python
from langchain_community.chat_models.ollama import ChatOllama

llm = ChatOllama(model="command-r-plus")
```

推論を実行

```python
response_message = llm.invoke("日本の首相は誰ですか？")
print(response_message.content)
```

出力

```python
岸田文雄 (きしだ ふみお) が、2021年10月4日から日本の第100代内閣総理大臣を務めています。
```

使用しなモデルをアンロードする場合はollama rmコマンドを使ってモデルを指定します。

```bash
$ ollama rm command-r-plus
deleted 'command-r-plus'

$ ollama list
NAME    ID      SIZE    MODIFIED 
```



# 実装の概説(Hugging Faceのパターン)

必要なライブラリのインストール。

```python
!pip install -Uq langchain_huggingface transformers torch
```

必要なクラスをimportします。Hugging Faceを利用する際のお決まりのクラス群です。

```python
import torch
from langchain_huggingface import HuggingFacePipeline
from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline
```

利用するモデルを指定します。今回は東京大学松尾研究室発のベンチャー企業Elyzaで開発されたLLMであるElyzaを使ってみます。Llama2ベースでファインチューニングされた7bの日本語モデルです。AutoTokenizerでトークナイザを、AutoModelForCausalLMでモデルをロードします。つまり、このコードを実行したタイミングでHugging Faceからモデルをダウンロードし、ローカル環境にデプロイされた状態になります。

```python
model_name = "elyza/ELYZA-japanese-Llama-2-7b-instruct"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(
    model_name,
    torch_dtype="auto",      # 自動で適切なデータ型を選択
    device_map="auto"        # 自動的に最適なデバイスに配置
)
```

上記コードを実行した後のGPUメモリ使用量が以下のように約13GB強です。

```bash
$ nvidia-smi
Thu Aug 22 15:57:53 2024       
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 550.90.07              Driver Version: 550.90.07      CUDA Version: 12.4     |
|-----------------------------------------+------------------------+----------------------+
| GPU  Name                 Persistence-M | Bus-Id          Disp.A | Volatile Uncorr. ECC |
| Fan  Temp   Perf          Pwr:Usage/Cap |           Memory-Usage | GPU-Util  Compute M. |
|                                         |                        |               MIG M. |
|=========================================+========================+======================|
|   0  NVIDIA A10                     Off |   00000000:00:04.0 Off |                    0 |
|  0%   31C    P0             54W /  150W |    6663MiB /  23028MiB |      0%      Default |
|                                         |                        |                  N/A |
+-----------------------------------------+------------------------+----------------------+
|   1  NVIDIA A10                     Off |   00000000:00:05.0 Off |                    0 |
|  0%   30C    P0             53W /  150W |    6663MiB /  23028MiB |      0%      Default |
|                                         |                        |                  N/A |
+-----------------------------------------+------------------------+----------------------+
                                                                                         
+-----------------------------------------------------------------------------------------+
| Processes:                                                                              |
|  GPU   GI   CI        PID   Type   Process name                              GPU Memory |
|        ID   ID                                                               Usage      |
|=========================================================================================|
+-----------------------------------------------------------------------------------------+
```

モデルのパラメータを定義します。
ちなみに、max_length(出力文章の最大長)が大きければ大きいほど消費するメモリも大きくなりますので要注意です。

```python
pipe = pipeline(
    "text-generation",
    model=model,
    tokenizer=tokenizer,
    max_length=500,
    do_sample=True,
    temperature=0.7,
    top_p=0.9,
    repetition_penalty=1.2,
)
```

ここでLangChainのHuggingFacePipelineを使って、モデルの定義をラップします。

```python
llm = HuggingFacePipeline(pipeline=pipe)
```

次に以下のようなプロンプトを定義してみます。

```python
prompt = "昔々あるところに"
inputs = tokenizer(prompt, return_tensors="pt", padding=True, truncation=True)
```

以上で準備完了です。
テキスト生成を実行します。

```python
response = llm.invoke(prompt) 
print(response)
```

出力としては下記のように少しユルめのテキストが生成されました。
とりあえず動作させるというところで何もチューニングしていないのでOSSのLLMだとこんなものでしょうか。

```python
昔々あるところに、猿の親子がいました。
それは幸せな時間を過ごす事が出来ていたんですけども、一日中ずっと袋から食べ物を取りだして食べてばかりいた弱っちいオスの父猿が亡くなってしまうという波瀾万丈なお話であります。
心配性の母猿は「今度こそ死ぬわ」と思いつめてしまい、自身も命を絶つという最後まで切ないお話であります。
結局ひょんなことから生き別れた赤子が元気に育っていることが判明してほっとしたりしたんですけどね。
え？何でそんな話をしているのかと言えば、先月発表された「2017年上半期アニメランキング」（https://anime.com/news/ranking-q3-2017） で『鬼灯の冷徹』が6位に入ったからなんですよ！
他にも名前を知っている作品が多数ランクインしているので、正直ビックリしました…。
本当に見事なまでに“人気”というジャンルで勝利を重ねている作品なので、みんな好きなんだな〜と改めて実感しました。
ただ私が『鬼灯』を初めて見たのは、テレビ放送開始の2年後でして、当時はややこの手のギャグ描写に慣れていなかったのも
```

因みに、推論中のメモリ使用量は約15.4GBと、2GBほど増えた状況です。


```bash
$ nvidia-smi
Thu Aug 22 16:10:22 2024       
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 550.90.07              Driver Version: 550.90.07      CUDA Version: 12.4     |
|-----------------------------------------+------------------------+----------------------+
| GPU  Name                 Persistence-M | Bus-Id          Disp.A | Volatile Uncorr. ECC |
| Fan  Temp   Perf          Pwr:Usage/Cap |           Memory-Usage | GPU-Util  Compute M. |
|                                         |                        |               MIG M. |
|=========================================+========================+======================|
|   0  NVIDIA A10                     Off |   00000000:00:04.0 Off |                    0 |
|  0%   57C    P0            190W /  150W |    7715MiB /  23028MiB |     51%      Default |
|                                         |                        |                  N/A |
+-----------------------------------------+------------------------+----------------------+
|   1  NVIDIA A10                     Off |   00000000:00:05.0 Off |                    0 |
|  0%   56C    P0             99W /  150W |    7673MiB /  23028MiB |     52%      Default |
|                                         |                        |                  N/A |
+-----------------------------------------+------------------------+----------------------+
                                                                                         
+-----------------------------------------------------------------------------------------+
| Processes:                                                                              |
|  GPU   GI   CI        PID   Type   Process name                              GPU Memory |
|        ID   ID                                                               Usage      |
|=========================================================================================|
+-----------------------------------------------------------------------------------------+
```


以上、ローカルLLM(プライベートLLM)についてOllamaとHuggingFacePipelineの2パターンの概説でした。