llama.cppを使ってIntel ArcでSwallow(13B)を動かしてみた

Intel Arc A770は4万円切りでVRAMが16GBと2024年初頭であっても唯一無二のGPUです。[^1]NvidiaでVRAM16GBと言えばRTX 4060Tiですが、こちらは6万円後半です。
初期はドライバが微妙なこともあり、あまり良い性能ではなかったですが最近では整備されたこともあり性能が向上してきました。

そんなA770でローカルLLMであるSwallowをllama.cppで動かしてみました。
## 環境
- Windows 11
- CPU: Ryzen 9 7900X
- GPU: Intel Arc A770 Limited Edition(16GB)

## llama.cppとは？
C/C++で書かれたLLMを動かすためのプログラムです（ざっくり）。
量子化したLLMモデルを実行できるので、CPUでも比較的高速に動かすことができます。
OpenCL BLASであるCLBlastを用いることで、Intel GPUでllama.cppを実行することができます。

## 導入手順
### CMakeのインストール
[公式サイト](https://cmake.org/download/)よりインストールします。
インストーラーに従えば良いです。

### OneAPIのインストール
[公式サイト](https://www.intel.com/content/www/us/en/developer/tools/oneapi/base-toolkit-download.html)よりIntel oneAPI Base Toolkitをインストールします。
バージョンは2024.0で動作確認しています。

メールアドレスと国名を入力する欄がありますが、「Continue without signing up (download starts immediately)」 をクリックすると入力せずにダウンロードできます。
以下の画面で、「Intel oneAPI DPC++/C++ Compiler」をインストールします。

![Intel® oneAPI Base Toolkit 2024_01_05 20_43_34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1857040/e0c84a36-94ff-6707-566c-e63c63baca71.png)


### CLBlastの準備
CLBlastのリポジトリよりgit cloneします。
`git clone https://github.com/CNugteren/CLBlast.git`

ビルドにはOpenCLのライブラリが必要なので先程インストールしたOneAPIにあるものを指定します。
```bash
cd CLBlast
mkdir build
cd build
cmake .. -DOPENCL_INCLUDE_DIRS="C:\Program Files (x86)\Intel\oneAPI\compiler\2024.0\include\sycl" -DOPENCL_LIBRARIES="C:\Program Files (x86)\Intel\oneAPI\compiler\2024.0\lib\OpenCL.lib"
cmake --build . --config Release
```

ビルドが終わったら、新しく以下のような階層でディレクトリを作成します。ディレクトリ名は何でも良いですが、ここではCLBlast_buildとしています。
```
CLBlast_build
├─bin
├─cmake
├─include
└─lib
```

`CLBlast/build/Release`にある`clblast.dll`をbinディレクトリ、`clblast.lib`をlibディレクトリに入れます。
`CLBlast/include`にある全ファイルをincludeディレクトリに入れます。
cmakeディレクトリには`CLBlastConfig.cmake`というファイルを作成し、以下のように入力します。
```cmake
set(CLBlast_INCLUDE_DIRS "${CMAKE_CURRENT_LIST_DIR}/../include")
set(CLBlast_LIBRARIES "${CMAKE_CURRENT_LIST_DIR}/../lib/clblast.lib")
```

### llama.cppのインストール
llama.cppのリポジトリよりgit cloneします。
```
git clone https://github.com/ggerganov/llama.cpp.git
cd llama.cpp
```

公式のCMakeList.txtではoneAPIを用いたビルドができないのでこれを書き換えます。
378行目（執筆時点）のLLAMA_CLBLASTの分岐のところを以下のように書き換えます。適時CLBlastのビルドしたディレクトリを読み替えてください。
```CMakeList.txt
if (LLAMA_CLBLAST)
    find_package(CLBlast)
    if (CLBlast_FOUND)
    	include_directories("C:/CLBlast_build/include" "C:/Program Files (x86)/Intel/oneAPI/compiler/2024.0/include/sycl")
        message(STATUS "CLBlast found")

        set(GGML_HEADERS_OPENCL ggml-opencl.h)
        set(GGML_SOURCES_OPENCL ggml-opencl.cpp)

        add_compile_definitions(GGML_USE_CLBLAST)

        #set(LLAMA_EXTRA_LIBS ${LLAMA_EXTRA_LIBS} clblast)
        link_libraries("C:/CLBlast_build/lib/clblast.lib" "C:/Program Files (x86)/Intel/oneAPI/compiler/2024.0/lib/OpenCL.lib")
    else()
        message(WARNING "CLBlast not found")
    endif()
endif()
```

ビルドします。
```
mkdir build
cd build
cmake .. -DLLAMA_CLBLAST=ON -DCMAKE_PREFIX_PATH="C:\CLBlast"
cmake --build . --config Release
```

`bin\Release`にexeプログラムが生成されます。同じ階層に`clblast.dll`を入れれば準備は完了です。

## 使ってみる
生成されたexeファイルがあるディレクトリで以下を実行します。今回は、modelディレクトリに[量子化されたSwallowモデル（13B、8bit）](https://huggingface.co/TheBloke/Swallow-13B-GGUF/tree/main)を置き、実行してみます。
```
set GGML_OPENCL_PLATFORM=Intel(R) OpenCL Graphics
main -m "C:\LLAMA\model\swallow-13b-instruct.Q8_0.gguf" -p "日本一高い山はなんですか。" -ngl 80 -t 1
```

これで実行してみると入力した文章が文字化けしてしまい、正しく認識されません（英語なら認識されます）。
そこで、外部ファイルにプロンプトを記述し、それを呼び出してみます。

```prompt.txt
日本一高い山はなんですか。
```

```
main -m "C:\LLAMA\model\swallow-13b-instruct.Q8_0.gguf" -f prompt.txt -ngl 80 -t 1
(端末情報省略)

 日本一高い山はなんですか。
富士山です。
では、次に高い山は何でしょうか?
北岳です。
日本の山を2番目から10位まで覚えていますか。
私も自信がありませんので、この場で一緒に調べてみましょう。
ちなみに、富士山と同じ日本一の物をご存じですか。
それは、湖です。
富士五湖はよく知られていますから、「河口湖」などを思い浮かべるかもしれませんが、日本で一番大きな湖は「琵琶湖」です。
2番目に大きいのが北海道にあるサロマ湖で、3番目が霞ケ浦です。
ところで、私たちの住んでいる地球上で、一番高い山といえば、エベレストです。
それでは、その高さを知っていますか?
標高8,849メートルです。
次に高い山はK2(ケイツー)で、その標高は8,611メートルです。
このような山の名前がなぜ英語表記なのでしょうか。
それは、測量した国名の順につけられているからです。
エベレストは、インド北部とネパールの国境付近に位置しますが、その昔イギリス領であったインドで測量されたので「Everest(エベレスト)」という名前になりました。
K2は、パキスタンと中国にまたがるカラコルム山脈にあり、測量した国名の順につけられていることから、ケイツーと呼ばれています。
標高1位~10位までをご紹介しましょう。
日本で一番高い山が富士山ですから、次に高い北岳も知っておきたいところです。
2位 北岳(3193メートル)
3位 奥穂高岳(3190メートル)
4位 間ノ岳(3189.5メートル)
5位 槍ヶ岳(3180メートル)
6位 悪沢岳(3141メートル)
7位 赤石岳(3120メートル)
8位 涸沢岳(311メートル)
9位 北穂高岳(310.6メートル)
10位 大喰岳(310.2メートル)
いかがですか?
富士山から数えて、3番目の高い山が「北岳」だと覚えておけば、きっと役に立つ時がきますよ。 [end of text]

llama_print_timings:        load time =    7891.24 ms
llama_print_timings:      sample time =      67.47 ms /   578 runs   (    0.12 ms per token,  8566.64 tokens per second)
llama_print_timings: prompt eval time =    1349.20 ms /    11 tokens (  122.65 ms per token,     8.15 tokens per second)
llama_print_timings:        eval time =   96787.75 ms /   577 runs   (  167.74 ms per token,     5.96 tokens per second)
llama_print_timings:       total time =   98347.29 ms
Log end
```

思いっきりハルシネーション起こしていますが、とりあえず動きました。

後から気づきましたが、実はhuggingfaceのREADMEを見るとQ8_0はファイルサイズが大きいからかnot recommendedでした。13BではQ5_K_Sが推奨されています。

### おまけ
[70Bモデル](https://huggingface.co/TheBloke/Swallow-70B-instruct-GGUF)動かしたらどうなるのかな、と思ってやってみました。モデルには「Q5_K_M」を使用しています。

`main -m "C:\LLAMA\model\swallow-70b-instruct.Q5_K_M.gguf" -f prompt.txt -ngl 32 -t 1`

あれ、SSDがめっちゃ熱くなってる……VRAMに乗り切らなくてメモリに乗せてるけど31.9GB/32.0GBで一杯になってる……

**結果：動かない！！**
70Bが個人PCで動かせる時代は来るのでしょうか……
## 参考文献
[Run LLama-2 13B, very fast, Locally on Low Cost Intel's ARC GPU , iGPU and on CPU](https://www.youtube.com/watch?v=FRWy7rzOsRs)
[普通ののWindows11（ノートPC）でllama.cppが動く](https://qiita.com/wonox/items/c655f80e98e19217e6cf)

[^1]: 普段は4.7万円程度で初売りの時に4万円を切っていました。ただNvidia GPUよりはセールをやっている印象。