70BのローカルLLMを動かす環境を格安に構築したい

# 本記事の概要
- 生成速度は遅くてもよいので，70B程度のLLMを動かすための環境を中古部品をかき集めてオンプレで構築することを試みた
- そのためのGPUとしてNVIDIA Tesla K80を複数台使用した
- LLMを動かすためのツールには独自にビルドしたOllamaを用いた。その環境を，Ubuntu22.0上のDockerコンテナとして構築した
- 13B（8bit量子化）モデルで11.0token/s，70B（8bit量子化）モデルで1.8token/s程度の生成速度を得た

　以下，導入経緯から記載しているので，取り急ぎ構築手順を参照されたい方は「[環境構築（ソフトウェア）](#環境構築ソフトウェア)」の章へ。

:::note info
初投稿です。Qiita初心者なのでお気づきの点などありましたらお知らせいただけますと幸いです。
:::

# はじめに
　LM Studioなど大規模言語モデル（LLM）をローカル環境で容易に使える環境が増えてきたこともあり，当方もこれらを活用した研究や授業を進めています。もともとクラウドを活用する方針だったのですが，学生さんには試行錯誤しながらいろいろ学んでもらいたいですし，目的によっては高い精度が求められて規模の大きなモデルを長時間にわたって動かしたい場合もあったりして用途がさまざまであり，課金管理の難しさを感じていました。そこで，生成速度は遅くてもよいので，70B程度のLLMを動かせる環境をなるべくお金をかけずにオンプレで用意できないか，というのが今回の動機です。

# なぜ NVIDIA Tesla K80 を使うのか
　あまり理由はありません・・・。中古のGPUを探していて，なるべく安く，なるべく大きなVRAMを積んているものを探していてこれに行きつきました。

* NVIDIA Tesla K80の仕様概要
  - 2014年11月リリース
  - CUDAコア：4,992ユニット（2,496ユニット×2）
  - メモリ：24GB GDDR5
  - バス：PCI-Express 3.0 x16
  - 単精度演算性能：5.6 TFlops
  - 倍精度演算性能：1.87 TFlops
  - TDP：300W

　今回は1基1万円前後で中古品を入手できました。当時はこの最強スペックに驚愕しましたが，いまやミドルクラスのRTX 4060でも約8倍の15.11TFlops（しかもTDPは115W）。10年ひと昔ですね。
　さて，K80は24GBのVRAMを積んている点は良いのですが，LLMを動かそうとすると以下の問題があります。
 - K80のCompute Capability(CC)は3.7
   - 3.7はさすがに古すぎのようです。例えばvLLMは CC 7.0 or higherとなっています[^vLLM]。今回使用するOllamaも，リリースバイナリをダウンロードするとそのバイナリは CC 5.0 以上の想定でビルドされています[^ollama_compati]。
 - K80は1台にGPUを2基実装する構成
   - K80は1台でグラフィックカードを2台積んでいるような構成で，メモリについても1GPUごとに12GBずつ実装されています。nvidia-smiで確認するとK80 1台で12GBメモリを積むGPUが2つ表示されます。このため，12GBを超えるLLMを扱う時にはこのモデルを分割して複数のGPUで扱う仕組みが必要になります（実際には，VRAM容量のすべてをLLMに使うわけではないので12GB未満でも分割が必要になる場合がある）
   
以上をふまえて環境を構築します。

[^vLLM]:https://docs.vllm.ai/en/latest/getting_started/installation.html
[^ollama_compati]:https://github.com/ollama/ollama/blob/main/docs/gpu.md

# 環境構築（ハードウェア）
### 中古品でマシンを構成する
　今回はとにかく格安でLLM環境を構築したかったので，マザーボード等も中古品でそろえて構築することにしました（動くか不明なので中古部品の入手は自腹で・・・）
- K80の中古品を4台入手
- K80はPCI-Express 3.0 x16なので，これを4 Slotフルに使用できるマザーボードを探したところ，Xeon E5-2690を2基積んだSuperMicro X9DRG-QF（難ありの中古品）を1万円強で入手。また，マザーボードで使うメモリとして，中古品のSKhynix PC3-12800R 16GBの12枚セットを入手
- このほか，電源，SSDなどは未使用のマシンから移植して構成


### K80の冷却問題
　K80はパッシブ冷却のため冷却ファンが搭載されていません。ネットを探すと3Dプリンタでエアダクトを造形してファンに繋げて冷却する方法が散見されますが，今回はPCケースなどを使用せずにオープンで動けばよいので，80mmファンを2基ずつ用意し，カバーを取り外してその取付けネジの穴を使ってワイヤーで固定する方法をとりました。
|<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3850392/99c5b353-e068-0701-255a-d3010a5e92cc.jpeg" />|<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3850392/d7048596-013b-42c2-e723-dd2d9490f664.jpeg" />|
|---|---|
|K80の冷却ファンの取付け|構築したマシン（全景）|


# 環境構築（ソフトウェア）
　以下，Ubuntu 22.04 LTS 上での環境構築手順です。

## NVIDIA driverとDockerのインストール

　Ubuntu22.04インストール後のNVIDIA DriverとDockerのインストール手順は既存の良記事と同様です。まず，NVIDIA Driverについて参考にした記事はこちら。

https://qiita.com/porizou1/items/74d8264d6381ee2941bd

https://qiita.com/tmasada/items/f77808c870c829c076fa

### Nouveauを無効化
　まず，Nouveauを無効化します。無効化するためにblacklist-nouveau.conf を作成します。

```Text:blacklist-nouveau.conf を作成
$ echo 'blacklist nouveau' | sudo tee -a /etc/modprobe.d/blacklist-nouveau.conf
$ echo 'options nouveau modeset=0' | sudo tee -a /etc/modprobe.d/blacklist-nouveau.conf
```

　作成後，作成されたファイルの内容を確認し，反映して，再起動します。

```Text: blacklist-nouveau.conf の確認・反映・再起動
$ cat /etc/modprobe.d/blacklist-nouveau.conf
blacklist nouveau
options nouveau modeset=0
$ sudo update-initramfs -u
$ sudo reboot
```

再起動後，以下のコマンドを入力して何も表示されないことを確認します。

```Text: Nouveauを無効化の確認
lsmod | grep nouveau
```

### NVIDIA driver のインストール
 　次に，NVIDIA Driverをインストールします。K80を使用するために，nvidia-driver-470を選択してインストールします。
 
```Text:NVIDIA driverをインストール
$ ubuntu-drivers devices
== /sys/devices/pci0000:00/0000:00:02.0/0000:02:00.0/0000:03:10.0/0000:05:00.0 ==
modalias : pci:v000010DEd0000102Dsv000010DEsd0000106Cbc03sc02i00
vendor   : NVIDIA Corporation
model    : GK210GL [Tesla K80]
driver   : nvidia-driver-470-server - distro non-free
driver   : nvidia-driver-418-server - distro non-free
driver   : nvidia-driver-450-server - distro non-free
driver   : nvidia-driver-390 - distro non-free
driver   : nvidia-driver-470 - distro non-free recommended
driver   : xserver-xorg-video-nouveau - distro free builtin

$ sudo add-apt-repository ppa:graphics-drivers/ppa
$ sudo apt update
$ sudo apt install -y nvidia-driver-470
$ sudo reboot
```
　再起動後，nvidia-smiコマンドにて確認します。
```Text:NVIDIA driverのインストール結果の確認
$ nvidia-smi
Tue Jul 30 14:19:55 2024
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 470.256.02   Driver Version: 470.256.02   CUDA Version: 11.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  Tesla K80           Off  | 00000000:04:00.0 Off |                    0 |
| N/A   36C    P8    26W / 149W |      4MiB / 11441MiB |      0%      Default |
|                               |                      |                  N/A |
+-------------------------------+----------------------+----------------------+
|   1  Tesla K80           Off  | 00000000:05:00.0 Off |                    0 |
| N/A   37C    P8    28W / 149W |      4MiB / 11441MiB |      0%      Default |
  :
  (中略)
  :
| Processes:                                                                  |
|  GPU   GI   CI        PID   Type   Process name                  GPU Memory |
|        ID   ID                                                   Usage      |
|=============================================================================|
|    0   N/A  N/A      1049      G   /usr/lib/xorg/Xorg                  3MiB |
|    1   N/A  N/A      1049      G   /usr/lib/xorg/Xorg                  3MiB |
+-----------------------------------------------------------------------------+
```
 
## Docker と NVIDIA Container Toolkit のインストール
　今回は，Docker上に環境を構築するのでDockerをインストールし，Docker上でGPUを使用できるよう構成します。

### Docker のインストール

[Install Docker Engine on Ubuntu](https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository)の手順に従ってインストールします。

 ``` shell:Dockerのインストール
> sudo apt-get update
$ sudo apt-get install ca-certificates curl
$ sudo install -m 0755 -d /etc/apt/keyrings
$ sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
$ sudo chmod a+r /etc/apt/keyrings/docker.asc

$ echo \
   "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
      $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
         sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

$ sudo apt-get update
$ sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
```

 これらのインストール手順の後，以下のコマンドで動作を確認します。
 
``` Text:Dcokerコンテナの動作確認
$ sudo docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
c1ec31eb5944: Pull complete
Digest: sha256:1408fec50309afee38f3535383f5b09419e6dc0925bc69891e79d84cc4cdcec6
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.
  :
(以下略）
```

　なお，今後の操作の簡略化のため，root権限でなくてもdockerを扱えるように以下のコマンドで設定します。dockerグループを作成し，そのグループに現在のユーザを追加しています。なお，dockerグループが既に存在する場合はその旨が表示されます。
 
```Text:dockerグループの作成とユーザ追加
$ sudo groupadd docker
$ sudo usermod -aG docker $USER
```
### NVIDIA Container Toolkitのインストール
　Dockerコンテナ内でGPUを使用する環境とするためにNVIDIA Container Toolkitをインストールします。[Installing the NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html)の手順に従いインストールします。
 ```Text:Container Toolkitのインストール
$ curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg \
    && curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \
      sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
      sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list

$ sudo apt-get update
$ sudo apt-get install -y nvidia-container-toolkit
 ```
　インストール後，以下のコマンドでランタイムを設定します。 
 ```Text:Container Runtimeの設定
 $ sudo nvidia-ctk runtime configure --runtime=docker
 $ sudo systemctl restart docker
```

### Dockerコンテナの動作確認

　ようやくGPUを使ってDockerコンテナを動作させる環境が整いました。以下のコマンドでGPUの動作に必要なドライバやツールが含まれているコンテナを起動して，コンテナ内でGPUが認識されているか確認します。なお，ここで使用するイメージは，この後の手順でOllamaのビルドや実行に使用することを踏まえて「nvcr.io/nvidia/pytorch:21.10-py3」選択しています[^21.10-py3]。「Detected NVIDIA Tesla K80 GPU, which is not supported by this container」と表示されますが，nvidia-smiおよびnvccの結果が表示されることを確認します。
 
[^21.10-py3]:[Frameworks Support Matrix](https://docs.nvidia.com/deeplearning/frameworks/support-matrix/index.html)に従うと，nvidia driver 470 に対応する最後のコンテナイメージがこれに該当するようです。

 ```Text:Dockerコンテナの起動とGPUの認識確認
$ docker run --rm --gpus all nvcr.io/nvidia/pytorch:21.10-py3 bash -c "nvidia-smi; nvcc -V"
Unable to find image 'nvcr.io/nvidia/pytorch:21.10-py3' locally
21.10-py3: Pulling from nvidia/pytorch
f3ef4ff62e0d: Pulling fs layer
0a5a58fe9f80: Pulling fs layer
ffe911d7d638: Pulling fs layer
b6ca3d91503f: Waiting
  :
  (中略)
  :
NVIDIA Release 21.10 (build 28019337)
PyTorch Version 1.10.0a0+0aef44c

Container image Copyright (c) 2021, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
     :
   (中略)
     :
ERROR: Detected NVIDIA Tesla K80 GPU, which is not supported by this container
ERROR: Detected NVIDIA Tesla K80 GPU, which is not supported by this container
ERROR: Detected NVIDIA Tesla K80 GPU, which is not supported by this container
     :
   (中略)
     :
Tue Jul 30 06:16:23 2024
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 470.256.02   Driver Version: 470.256.02   CUDA Version: 11.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  Tesla K80           Off  | 00000000:04:00.0 Off |                    0 |
| N/A   35C    P8    26W / 149W |      4MiB / 11441MiB |      0%      Default |
|                               |                      |                  N/A |
+-------------------------------+----------------------+----------------------+
|   1  Tesla K80           Off  | 00000000:05:00.0 Off |                    0 |
| N/A   35C    P8    28W / 149W |      4MiB / 11441MiB |      0%      Default |
     :
   (中略)
     :
+-----------------------------------------------------------------------------+
| Processes:                                                                  |
|  GPU   GI   CI        PID   Type   Process name                  GPU Memory |
|        ID   ID                                                   Usage      |
|=============================================================================|
+-----------------------------------------------------------------------------+
nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2021 NVIDIA Corporation
Built on Sun_Aug_15_21:14:11_PDT_2021
Cuda compilation tools, release 11.4, V11.4.120
Build cuda_11.4.r11.4/compiler.30300941_0
 ```


# K80用にOllamaをビルドする

　いよいよLLMを動かすためのOllamaのビルドです。前述したように，OllamaのバイナリはCC5.0以上を想定してビルドされています（2024年7月31に現在）[^ollama_compati]。K80はCC3.7なので，これに対応させるために，独自にバイナリをビルドしなければなりません。K80などの古いGPUのためのビルド方法については「[Older CUDA compute capability 3.5 and 3.7 support #1756](https://github.com/ollama/ollama/issues/1756)」のスレッドで議論があり，以下の手順はこれを踏まえたものです。


### 環境構築用のDockerコンテナの準備
　Ollamaのビルドとその後の動作環境のために，専用のコンテナをDocker Composeを用いて作成します。まず，Docker Composeをインストールしておきます。

```
$ sudo apt install -y docker-compose
```
 　Ollamaのビルドには，
  
- cmake version 3.24 or higher
- go version 1.22 or higher
- gcc version 11.4.0 or higher

が必要[^ollama_develop]なので，これらのインストールをnvcr.io/nvidia/pytorch:21.10-pyに対して行います。なお，以下の作業ではホスト側に「~/ollama_rebuild」ディレクトリを作成してその中で行う想定で説明します。「~/ollama_rebuild/work」はコンテナにマウントする作業用ディレクトリです。

[^ollama_develop]:https://github.com/ollama/ollama/blob/main/docs/development.md

```Text:ディレクトリの準備
$ mkdir ~/ollama_rebuild
$ mkdir ~/ollama_rebuild/work
$ cd ~/ollama_rebuild
```
　Dockerfile，docker-compose.yml，.go_env ファイルをそれぞれ作成します。
```docker:Dockerfile
# ベースイメージ(CUDA)の指定
FROM nvcr.io/nvidia/pytorch:21.10-py3

RUN ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime

# Install cmake
RUN apt-get update && apt-get install -y cmake

# Install go
RUN apt-get install -y software-properties-common
RUN add-apt-repository ppa:longsleep/golang-backports
RUN apt update
RUN apt-get install -y golang

# Install gcc11
RUN apt install -y build-essential manpages-dev software-properties-common
RUN add-apt-repository ppa:ubuntu-toolchain-r/test
RUN apt update && apt install -y gcc-11 g++-11
RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-11 110

RUN mkdir /work

# 作業ディレクトリを設定
WORKDIR /work
ADD . /work

# コンテナの起動時にbashを実行
CMD ["/bin/bash"]
```

```yml:docker-compose.yml
services:
  ollama_k80_env_v0.3.0:
    build:
      context: .
      dockerfile: ./Dockerfile
    runtime: nvidia
    container_name: ollama_k80_env_v0.3.0
    volumes:
      - ./work:/work
    tty: true
    env_file: ./.go_env
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: all
              capabilities: [gpu]
```
 

```shell:.go_env
GOPATH=/go
```

　作成後docker-composeでコンテナイメージをビルドし，ビルドが成功したらコンテナを起動します。

```Text:コンテナのビルドと起動
$ docker-compose build --no-cache
$ docker-compose up -d
Creating ollama_k80_env_v0.3.0 ... done
```
　コンテナの起動を確認してコンテナのシェルを起動し，コンテナ内での作業に移ります。

 ```Text:コンテナの確認とコンテナのシェルの起動
$ docker ps
CONTAINER ID   IMAGE                                  COMMAND                   CREATED          STATUS          PORTS                NAMES
19c61a71a477   ollama_rebuild_ollama_k80_env_v0.3.0   "/opt/nvidia/nvidia_…"   21 seconds ago   Up 20 seconds   6006/tcp, 8888/tcp   ollama_k80_env_v0.3.0
$ docker exec -it ollama_k80_env_v0.3.0 /bin/bash
```

　コンテナのシェルにシェルに入ったら，念のためnvidia driver, cmake, gcc, go のバージョンを確認しておきます。

 ```Text:コンテナ内の各ツールの確認
$ nvidia-smi
Tue Jul 30 17:57:59 2024
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 470.256.02   Driver Version: 470.256.02   CUDA Version: 11.4     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
     :
   (中略)
     :
|=============================================================================|
+-----------------------------------------------------------------------------+
$ cmake --version
cmake version 3.21.3

CMake suite maintained and supported by Kitware (kitware.com/cmake).
$ gcc --version
gcc (Ubuntu 11.4.0-2ubuntu1~20.04) 11.4.0
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ go version
go version go1.22.5 linux/amd64
```

### Ollamaのビルド

　以下の手順でビルドを進めます。作業場所を「/work」ディレクトリに移して作業しています。まず，OllamaのGitHubからOllamaのリポジトリのクローンを作成します。また，上記記載の[スレッドの投稿](https://github.com/ollama/ollama/issues/1756#issuecomment-1913781785)からパッチファイルを取得します。そして，タグ付けされたv0.3.0に対してパッチを適用しています。
 
 ```Text:ollamaのソース取得とパッチ適用
$ cd /work
$ git clone https://github.com/ollama/ollama.git
$ cd ollama
$ git checkout -b ollama_v0.3.0_cc37build refs/tags/v0.3.0
Switched to a new branch 'ollama_v0.3.0_cc37build'
$ git describe --tags --abbrev=0
v0.3.0
$ curl -OL https://patch-diff.githubusercontent.com/raw/ollama/ollama/pull/2233.patch
$ git apply 2233.patch
 ```
 　次に，CC3.7に対応させるためgpu/gpu.goの実装を一部修正します。

 ```diff_go:gpu/gpu.go 63-64行目
#（修正前）
- var CudaComputeMajorMin = "5"
- var CudaComputeMinorMin = "0"

#（修正後）
+ var CudaComputeMajorMin = "3"
+ var CudaComputeMinorMin = "7"
 ```
   
  その後ビルドを行います。試していませんが，CMAKE_CUDA_ARCHITECTURES="37;50;60;70"などとすれば，それぞれのCCにも対応したバイナリになるのではないかと思われます[^ollama_develop]。

```Text:ollamaのビルド
$ export CMAKE_CUDA_ARCHITECTURES="37"
$ OLLAMA_CUSTOM_CUDA_ARCH="37" go generate ./...
go: downloading golang.org/x/sys v0.20.0
go: downloading github.com/google/uuid v1.1.2
     :
   (中略)
     :
go generate completed.  LLM runners: cpu cpu_avx cpu_avx2 cuda_v11

$ go build -ldflags "-w -s -X=github.com/ollama/ollama/gpu.CudaMinVersion=3.7" .
```
  
 ### Ollamaの起動確認

  次のコマンドでollamaを起動します。起動ログの中で「inference compute」としてK80が列挙されていれば互換性あるGPUとしてOllamaから認識されています。

```Text:ollamaの起動
$ ./ollama serve 
2024/07/30 18:45:08 routes.go:1099: 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:/root/.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-07-30T18:45:08.696+09:00 level=INFO source=images.go:784 msg="total blobs: 0"
     :
   (中略)
     :
time=2024-07-30T18:45:14.736+09:00 level=INFO source=gpu.go:208 msg="looking for compatible GPUs"
time=2024-07-30T18:45:15.668+09:00 level=INFO source=types.go:105 msg="inference compute" id=GPU-2f962b3b-3aa0-9623-19e4-7641390c62df library=cuda compute=3.7 driver=11.4 name="Tesla K80" total="11.2 GiB" available="11.1 GiB"
time=2024-07-30T18:45:15.668+09:00 level=INFO source=types.go:105 msg="inference compute" id=GPU-687037da-e4a4-fe3d-d4cb-1db356624c11 library=cuda compute=3.7 driver=11.4 name="Tesla K80" total="11.2 GiB" available="11.1 GiB"
     :
   (以下略)
```
　この状態で，別途シェルを立ち上げるか，「ctrl+z」を押したあと「bg」コマンドを実行して「./ollama serve」をバックグランド実行へ移行させた後，以下のコマンドを実行することで言語モデルがダウンロード，起動され，簡易チャットが可能です。
```Text:簡易チャットの試行（llama3）
$  ./ollama run llama3
     :
   (中略)
     :
ggml_cuda_init: found 1 CUDA devices:
  Device 0: Tesla K80, compute capability 3.7, VMM: yes
? llm_load_tensors: ggml ctx size =    0.27 MiB
? llm_load_tensors: offloading 32 repeating layers to GPU
llm_load_tensors: offloading non-repeating layers to GPU
llm_load_tensors: offloaded 33/33 layers to GPU
llm_load_tensors:        CPU buffer size =   281.81 MiB
llm_load_tensors:      CUDA0 buffer size =  4155.99 MiB
     :
   (中略)
     :
>>> Send a message (/? for help)
```

 メッセージ文を入れると回答が得られます。下記の例では，8.6 token/s をぐらいのパフォーマンスを得ています。
 
 ```
 >>> Hello!
Hello! It's nice to meet you. Is there something I can help you with, or would you like to chat?
[GIN] 2024/07/31 - 22:03:26 | 200 |  2.921459443s |       127.0.0.1 | POST     "/api/chat"
```

# K80 4基とOllamaを用いた日本語モデルの簡易評価

　構築した環境において，いくつかの日本語モデルを用いて簡易的な評価を行いました。プロセスを起動してモデルを読み込んだ直後の最初のメッセージ（「こんにちは」）に対する生成は対象にせず，その次のメッセージに対する生成時間を計測しました。比較のため，RTX 4060Ti（16GB）を2基積んだWindowsマシン（Core i7-11700K, 32GB RAM, 仮想メモリ割り当てなし）を用意し，Ollamaを実行（Docker上ではなくネイティブアプリとして実行）した場合の結果を併せて計測しました。それぞれ3回計測した結果の平均値（token/sec）を下表に示します。

| モデル名 | K80 ×4 | RTX 4060 Ti ×2 |
|:-:|:-:|:-:|
|lama-3-Swallow-70B-Instruct-v0.1-Q8_0[^swallow_70B]|1.8| out of memory  |
|Llama-3-Swallow-70B-Instruct-v0.1-Q4_K_S[^swallow_70B]| 2.0| 2.9|
|ELYZA-japanese-Llama-2-13b-fast-instruct-q8_0[^elyza_13b]| 11.0|33.5 |

[^swallow_70B]:[mmnga/Llama-3-Swallow-70B-Instruct-v0.1-gguf](https://huggingface.co/mmnga/Llama-3-Swallow-70B-Instruct-v0.1-gguf)
[^elyza_13b]:[mmnga/ELYZA-japanese-Llama-2-13b-fast-instruct-gguf](https://huggingface.co/mmnga/ELYZA-japanese-Llama-2-13b-fast-instruct-gguf)

　RTX4060Ti×2のマシンはやはり70B Q8モデルの読み込みは不可でした。70B Q4モデルも困難かと思われましたが，2つのGPUのVRAMの合計を超えた分をRAMに読み込んで動作しているようでした。しかし，生成中はCPUの使用率が高くGPU利用率は20％程度であり，ほぼCPUを使って生成されているように思われます。実際13B Q8モデルのときはGPU使用率が70%以上と高く，CPU使用率は数％程度で，生成速度も33.5token/secと良好な結果となりました。
　一方，肝心のK80マシンですが，70B Q8モデルを実行できたものの1.8token/sec程度となりました。70B Q4モデルとの比較ではそれほど差が出ていないのでK80の複数台構成による効果は得られていると思いますが，これを使用する場合はそれなりの「遅さ」を踏まえることが必要になりそうです。
　K80マシンの生成中の動作を見ていると，合計16あるCPUコア[^2]のうち1つだけが100％になっていました。また，GPUの使用率は，すべてのGPUが同時に100％になるということはなく，100％になるGPUが次から次へと移動していくような動きでした。K80はCC3.7と古いためGPUで対応できない命令はCPUで扱われていたはずであり，この辺りの制御がパフォーマンスにも大きく影響していそうです。 

[^2]:Hyper Threadingは無効にしています。
 
### 生成例

　以下，各モデルの生成結果の例を掲載します。ELYZA-japanese-Llama-2-13b-fast-instruct-q8_0は，癒してくれないのですね。おっしゃる通りで・・・。

```Text:lama-3-Swallow-70B-Instruct-v0.1-Q8_0 (K80 ×4)
>>> こんにちは
こんにちは！私は日本語を話せます。何か助けることができますか？

>>> # 命令
... ・私は研究者です。研究が行き詰った時にハッピーになれる話題を，次の条件にしたがって教えてください。
...
... # 条件
... ・日本語で述べてください
... ・400字前後で述べてください
... ・楽しくなれる話題を述べてください
こんにちは！研究者様、頑張っているんですね。研究が行き詰った時は、少し息抜きしてハッピーになれる話題を提供します。

先日、私はAIの研究に堪能な友人と会い、面白いディスカッションをしました。彼はAIを利用した料理レシピ生成アプリケーションを作り上げました。このアプリケーションでは、ユーザー
が好きな食材や調味料を選ぶだけで、美味しいレシピを作成してくれます。彼によれば、このアプリケーションは世界中の料理文化を学びたい人々に役立つと考えています。

私はこの話題を聞いて、とてもハッピーになりました。AIは人間ができないことを助けることができる技術であり、研究者たちは常に新しいアイデアや技術を開発しています。このような話
題を聞くと、私たちも研究者としての自信と熱意を持って進み続けられるようになるでしょう。

また、私は最近、猫の写真を見て癒されました。インターネット上には世界中の猫たちの写真があります。私はこれらの写真を見ることで、心が和み、仕事にも集中できるようになりました
。猫は人間にとって特別な存在で、彼らは常に私たちを微笑ませてくれます。

研究が行き詰った時は、少し息抜きして楽しい話題や写真を見て、心を和ませることをお勧めします。そうすることで、新しいアイデアや熱意を持って仕事に取り組むことができます。頑張
ってください！
```

```Text:Llama-3-Swallow-70B-Instruct-v0.1-Q4_K_S (K80 ×4)
>>> こんにちは
こんにちは！日本語で応答します。何をお手伝いできますか？

>>> # 命令
... ・私は研究者です。研究が行き詰った時にハッピーになれる話題を，次の条件にしたがって教えてください。
...
... # 条件
... ・日本語で述べてください
... ・400字前後で述べてください
... ・楽しくなれる話題を述べてください
こんにちは！研究者として、行き詰った時は少しでもハッピーになれるような話題をお勧めします。

1. 旅行：次の旅行先を計画しましょう。新しい場所、新しい文化、新しい食べ物など、期待感とワクワク感を得られます。
2. 音楽：お気に入りの音楽を聴きましょう。心に響く曲や、新しいジャンルの音楽を発掘することで、リラックスして楽しくな
れます。
3. 食事：好きな食べ物を作りましょう。料理は、ストレスを和らげて、美味しいものを味わうことができます。
4. 運動：軽い運動を始めましょう。ジョギングやヨガなどの簡単な運動から、徐々にチャレンジしていくことで、気分転換と体
力増進が得られます。
5. 映画や本：新しい映画や小説に出会いましょう。心に響くストーリーや、興味深い知識を学びながら、楽しく過ごせます。
6. 友人：友達と話しましょう。共有する思い出や、新しい話題から、笑いや感動を得ることができます。

これらの話題は、研究の行き詰った時に少しでもハッピーになれるものですので、是非お勧めします。

```
```Text:ELYZA-japanese-Llama-2-13b-fast-instruct-q8_0(RTX4060Ti x2)
>>> こんにちは
おはようございます。

>>> # 命令
... ・私は研究者です。研究が行き詰った時にハッピーになれる話題を，次の条件にしたがって教えてください。
...
... # 条件
... ・日本語で述べてください
... ・400字前後で述べてください
... ・楽しくなれる話題を述べてください
「研究が行き詰った」とありますが、そもそも行き詰る research とはなんですか？何かを見つけることや、解決することを目的
とした作業や活動が Research です。つまり、行き詰るということは、その調査や検討がまだ足りていないということになります
。なので、「調べる」「探す」「解く」というワードでも良いのではないかと思います。「答えがない」という状況ではないはず
なので「楽しくなる話題」でいいのでは。
```

# おわりに

　本記事のまとめです。
 
- NVIDIA Tesla K80 を4台使用して70B程度のLLMを動かすための環境を構築することを試みた
- LLMを動かすためのツールとして独自にビルドしたOllamaを用いた。その環境をUbuntu22.0上のDockerコンテナとして構築した
- 13B（8bit量子化）モデルで11.0token/s，70B（8bit量子化）モデルで1.8token/s 程度の生成速度となった
  -  電力消費を無視でき生成速度が遅くてもよいのであれば，一応は結果が得られることがわかった
  - K80はCompute Capability(CC)が3.7であり，VRAMが多くてもGPUにオフロードされない命令も多くパフォーマンスが得られにくいと考えられる。これを裏付ける結果となった

　結論：K80はやはり古すぎた。

　長文にお付き合いくださりありがとうございました。