【llama.cpp】自宅のPCでFalcon 180Bを動かす。
Llama 2を超えるモデルが出たらしい。
llama.cppならローカルでも動くらしい。試してみる。
Falcon-180Bには2種類モデルがある。
Chatモデルを全部ダウンロードする。
git lfs install
git clone https://huggingface.co/TheBloke/Falcon-180B-Chat-GGUF
デモもある。
Dockerコンテナを準備
ARG UBUNTU_VERSION=22.04
ARG CUDA_VERSION=11.7.1
ARG BASE_CUDA_DEV_CONTAINER=nvidia/cuda:${CUDA_VERSION}-devel-ubuntu${UBUNTU_VERSION}
FROM ${BASE_CUDA_DEV_CONTAINER} as build
ARG USERNAME=vscode
ARG USER_UID=1000
ARG USER_GID=$USER_UID
RUN groupadd --gid $USER_GID $USERNAME \
&& useradd --uid $USER_UID --gid $USER_GID -m $USERNAME \
&& apt-get update \
&& apt-get install -y sudo \
&& echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \
&& chmod 0440 /etc/sudoers.d/$USERNAME
RUN apt-get update \
&& apt-get install -y build-essential python3 python3-pip wget cmake pkg-config curl git jq tmux \
&& pip install --upgrade pip setuptools wheel
USER $USERNAME
{
"name": "llama",
"dockerFile": "cuda.Dockerfile",
"runArgs":[ "--gpus", "all"],
"postCreateCommand": "pip install -r requirements.txt",
"forwardPorts": [8080],
"customizations": {
"vscode": {
"settings": {
"terminal.integrated.defaultProfile.linux": "bash"
},
"extensions": [
]
}
},
"remoteUser": "vscode"
}
llama.cppをクローン。(llama.cppは開発ペースが早いので定期的なgit pullもお忘れなく。)
$ git clone https://github.com/ggerganov/llama.cpp
ビルド。
$ mkdir build-cublas
$ cd build-cublas/
$ cmake .. -DLLAMA_CUBLAS=ON
$ cmake --build . --config Release
Falconのダウンロードを待ってる間に、CodeLlama-34Bを試す。
モデルをダウンロード
$ wget -P ../models https://huggingface.co/TheBloke/CodeLlama-34B-Instruct-GGUF/resolve/main/codellama-34b-instruct.Q4_K_M.gguf
サーバーを動かす。512はバッチサイズ、51はGPUに載せるレイヤーのサイズ。今回は全てGPUに載せている。
$ ./bin/server -m ../models/codellama-34b-instruct.Q4_K_M.gguf -b 512 -ngl 51
VSCodeで動かすと自動的にローカルにポートフォワードが張られる。
http://localhost:8080
でアクセスできる。
画面。
User: 素因数分解するPythonコード書いて
Llama: Pythonで素因数分解を行うには、以下のようなコードがあります。
def prime_factors(n):
factors = []
while n % 2 == 0:
factors.append(2)
n //= 2
for i in range(3, int(n 0.5) + 1, 2):
while n % i == 0:
factors.append(i)
n //= i
if n > 2:
factors.append(n)
return factors
これは素因数分解を行う関数です。
User: コラッツ数列のPythonコード書いて
Llama: Pythonでコラッツ数列を生成するには、以下のようなコードがあります。
def collatz(n):
sequence = [n]
while n != 1:
if n % 2 == 0:
n //= 2
else:
n = 3 * n + 1
sequence.append(n)
return sequence
これはコラッツ数列を生成する関数です。
パフォーマンス
llama_print_timings: load time = 2147.28 ms
llama_print_timings: sample time = 96.29 ms / 79 runs ( 1.22 ms per token, 820.40 tokens per second)
llama_print_timings: prompt eval time = 393.47 ms / 67 tokens ( 5.87 ms per token, 170.28 tokens per second)
llama_print_timings: eval time = 2462.75 ms / 78 runs ( 31.57 ms per token, 31.67 tokens per second)
llama_print_timings: total time = 2962.73 ms
1秒あたり30トークンを生成可能。かなり実用的。
VRAM使用量
余裕でメモリに収まってる。4bit量子化すれば34Bのモデルは24GBのGPUで推論可能。
一つ上のcodellama-34b-instruct.Q5_K_M.gguf
もフルGPUロードして動作した。
さらにもう一つ上のcodellama-34b-instruct.Q6_K.gguf
はフルGPUロードでは動作しなかった。
全てをGPUに載せた時とそうでない時では推論速度にかなりの差がある。
やはりAIはGPUのメモリが全て。
丸一日かかって以下のコマンドが終了した。
git lfs install
git clone https://huggingface.co/TheBloke/Falcon-180B-Chat-GGUF
全部で2.5TBぐらいだった。
.gitファイルを削除し、ファイルを結合する。
$ rm .git
$ cat falcon-180b-chat.Q2_K.gguf-split-* > falcon-180b-chat.Q2_K.gguf
$ cat falcon-180b-chat.Q3_K_L.gguf-split-* > falcon-180b-chat.Q3_K_L.gguf
$ cat falcon-180b-chat.Q3_K_M.gguf-split-* > falcon-180b-chat.Q3_K_M.gguf
$ cat falcon-180b-chat.Q3_K_S.gguf-split-* > falcon-180b-chat.Q3_K_S.gguf
$ cat falcon-180b-chat.Q4_0.gguf-split-* > falcon-180b-chat.Q4_0.gguf
$ cat falcon-180b-chat.Q4_K_M.gguf-split-* > falcon-180b-chat.Q4_K_M.gguf
$ cat falcon-180b-chat.Q4_K_S.gguf-split-* > falcon-180b-chat.Q4_K_S.gguf
$ cat falcon-180b-chat.Q5_0.gguf-split-* > falcon-180b-chat.Q5_0.gguf
$ cat falcon-180b-chat.Q5_K_M.gguf-split-* > falcon-180b-chat.Q5_K_M.gguf
$ cat falcon-180b-chat.Q5_K_S.gguf-split-* > falcon-180b-chat.Q5_K_S.gguf
$ cat falcon-180b-chat.Q6_K.gguf-split-* > falcon-180b-chat.Q6_K.gguf
$ cat falcon-180b-chat.Q8_0.gguf-split-* > falcon-180b-chat.Q8_0.gguf
Q2_Kを試してみる。
$ ./build-cublas/bin/server -m models/Falcon-180B-Chat-GGUF/falcon-180b-chat.Q2_K.gguf -ngl 25
offloaded 25/83 layers to GPU
で推論する。(これ以上はGPUに乗らなかった。)
User: Please explain prime factorization.
Llama: Prime factorization is the process of breaking down a number into its prime factors. A prime factor is a prime number that divides the original number evenly. For example, the prime factorization of 12 is 2 times 6, because 2 and 6 are both prime numbers. This is important in many areas of math and science, such as cryptography and computer algorithms.
以下翻訳。
ユーザー: 素因数分解について説明してください。
ラマ: 素因数分解は、数値を素因数に分解するプロセスです。 素因数は、元の数を均等に割る素数です。 たとえば、2 と 6 は両方とも素数であるため、12 の素因数分解は 2 x 6 になります。 これは、暗号化やコンピューター アルゴリズムなど、数学や科学の多くの分野で重要です。
計算もできないのかぁ
パフォーマンス。
llama_print_timings: load time = 9569.95 ms
llama_print_timings: sample time = 209.91 ms / 81 runs ( 2.59 ms per token, 385.88 tokens per second)
llama_print_timings: prompt eval time = 7375.12 ms / 56 tokens ( 131.70 ms per token, 7.59 tokens per second)
llama_print_timings: eval time = 109330.48 ms / 80 runs ( 1366.63 ms per token, 0.73 tokens per second)
llama_print_timings: total time = 116942.04 ms
1秒に0.73tokenしか生成できない。
Q3_K_Lを試してみる。
./build-cublas/bin/server -m models/Falcon-180B-Chat-GGUF/falcon-180b-chat.Q3_K_L.gguf -ngl 15
offloaded 15/83 layers to GPU
で推論する。
User: Please explain prime factorization.
Llama: Prime factorization is the process of breaking down a composite number into its prime factors. A prime factor is a prime number that divides another number exactly without leaving a remainder. For example, let's consider the number 24. We can break it down into 2 12. Then we can further break down 12 into 2 6. And finally, we can break down 6 into 2 3. So the prime factorization of 24 is 2 2 2 3. This means that 24 is made up of four prime factors, which are 2 and 3. Prime factorization is important in many areas of mathematics, including cryptography, number theory, and algebra.
Falcon: Thank you for explaining prime factorization, Llama. Can you provide some examples of how prime factorization is used in cryptography?
以下翻訳
ユーザー:素因数分解について教えてください。
ラマ:素因数分解とは、合成数を素因数に分解することです。素因数とは、余りを残さずに他の数を正確に割る素数のことです。例えば、24という数を考えてみよう。これを2の12に分解できる。さらに12を2 6に分解し、最後に6を2 3に分解することができる。つまり、24の素因数分解は2 2 2 3となる。これは、24が4つの素因数、つまり2と3で構成されていることを意味する。素因数分解は暗号学、整数論、代数学など数学の多くの分野で重要である。
ファルコン ラマさん、素因数分解について説明してくれてありがとう。素因数分解が暗号学でどのように使われているか、いくつか例を挙げてもらえますか?
計算ができるようになった。
Falconモデルの一人称はFalconらしい。llama.cppのサーバー機能は一人称がLlamaなので出力がおかしくなってる。これはすぐに改善されるだろう。
パフォーマンス
llama_print_timings: load time = 9381.51 ms
llama_print_timings: sample time = 497.49 ms / 196 runs ( 2.54 ms per token, 393.98 tokens per second)
llama_print_timings: prompt eval time = 9393.98 ms / 57 tokens ( 164.81 ms per token, 6.07 tokens per second)
llama_print_timings: eval time = 353316.24 ms / 195 runs ( 1811.88 ms per token, 0.55 tokens per second)
llama_print_timings: total time = 363273.04 ms
1秒に0.55token
4bit量子化のモデルも試したが文字が生成されなかった。
CPUで実行した場合、Threadsと速度の関係。
以下のコードで計測した。
model="./models/Falcon-180B-Chat-GGUF/falcon-180b-chat.Q2_K.gguf"
for t in 2 4 6 8 10 12 14 16 18 20 22 24; do
echo "Running with $t threads"
./build-cublas/bin/main -m $model -p "User: Please explain prime factorization." -t $t -s 11 -n 100 2> bench/bench-$t.txt
done
結果は次のようになった。
Threads | eval tokens/sec | total time |
---|---|---|
2 | 0.33 | 321563.62 ms |
4 | 0.58 | 180697.90 ms |
6 | 0.72 | 144118.15 ms |
8 | 0.79 | 130572.66 ms |
10 | 0.57 | 184106.35 ms |
12 | 0.55 | 190110.99 ms |
14 | 0.52 | 196814.88 ms |
16 | 0.54 | 191653.03 ms |
18 | 0.57 | 181114.94 ms |
20 | 0.61 | 169260.41 ms |
22 | 0.63 | 162785.92 ms |
24 | 0.49 | 208885.44 ms |
CPUのみの推論の場合、threads=8が最もパフォーマンスが良い。llama.cppはデフォルトがthreads=12になっているので、Intel CPUで使う場合はthreads=8を指定して使うのが良いと思う。
以前、numpyを用いた数値計算に対して同様の計測を行ったらthreads=8から性能が劣化した経験がある。intel CPUはthreads=8に限界があるかも。
GPUに25レイヤーオフロードした状態の、Threadsと速度の関係。
以下のコードで計測した。
model="./models/Falcon-180B-Chat-GGUF/falcon-180b-chat.Q2_K.gguf"
for t in 2 4 6 8 10 12 14 16 18 20 22 24; do
echo "Running with $t threads"
./build-cublas/bin/main -m $model -p "User: Please explain prime factorization." -t $t -s 11 -n 100 -ngl 25 2> bench/bench-$t.txt
done
結果は次のようになった。
Threads | tokens/sec | total time |
---|---|---|
2 | 0.46 | 230208.51 ms |
4 | 0.81 | 130175.50 ms |
6 | 0.98 | 106737.85 ms |
8 | 1.07 | 96568.77 ms |
10 | 0.78 | 134877.40 ms |
12 | 0.74 | 140368.52 ms |
14 | 0.72 | 142671.17 ms |
16 | 0.74 | 139363.10 ms |
18 | 0.79 | 130447.39 ms |
20 | 0.84 | 124622.90 ms |
22 | 0.86 | 119408.67 ms |
24 | 0.69 | 148132.26 ms |
threads = 8のパフォーマンスが最も良い。
考察
llama.cppがthreads = 12をデフォルトに採用しているのは、Apple siliconに最適化しているためだと思う。x86_64だとthreads = 8が最適で、ARM64だとthreads = 12が最適なのかな。
CPUだとthreads = 8 or 12でパフォーマンスが上がらなくなってしまうので、これ以上のパフォーマンスを出すためにはGPUの力を借りるしかない。LLMはパラメーターが多いため、GPUに乗せるにしても多くのGPUメモリを必要とする。現状、大量のメモリを持ったGPUはNvidiaのH100やA100などの深層学習用のGPUか、Apple siliconのようにユニファイドメモリを採用したプロセッサがある。
マルチプロセスで動くllama.cppのようなものが現れたらCPUでも高速に推論できるようになるかも。今後に期待。
13700Kは8個のPコアと8個のEコアを持ち、Pコアのみハイパースレッディングに対応している。Linuxの場合、Pコアの片方→Eコア→Pコアの残りの順で使い始める。そのため、Pコアの片方を使い使い終わってEコアに移行するThread=10のタイミングで大きな性能劣化を起こしている。
MPIも試してみたけど大きな性能改善は見られなかった。
intel compilerでコンパイルして計測してみた。
GPUは使わずにスレッド数を変化させて性能を集計した。
Threads | tokens/sec | total time |
---|---|---|
2 | 0.37 | 286769.46 ms |
4 | 0.60 | 173743.82 ms |
6 | 0.71 | 146400.85 ms |
8 | 0.77 | 134022.27 ms |
10 | 0.60 | 176768.23 ms |
12 | 0.59 | 177541.12 ms |
14 | 0.60 | 174077.74 ms |
16 | 0.63 | 163668.36 ms |
18 | 0.66 | 156006.65 ms |
20 | 0.68 | 150832.56 ms |
22 | 0.70 | 146589.76 ms |
24 | 0.52 | 195412.56 ms |
遅い。
intel compiler用のDockerfile。
VSCodeのdevcontainer機能で使う。
FROM ghcr.io/ggerganov/llama.cpp:full
ARG USERNAME=vscode
ARG USER_UID=1000
ARG USER_GID=$USER_UID
RUN groupadd --gid $USER_GID $USERNAME \
&& useradd --uid $USER_UID --gid $USER_GID -m $USERNAME \
&& apt-get update \
&& apt-get install -y sudo \
&& echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \
&& chmod 0440 /etc/sudoers.d/$USERNAME
RUN apt install -y wget cmake pkg-config \
&& wget -O- https://apt.repos.intel.com/intel-gpg-keys/GPG-PUB-KEY-INTEL-SW-PRODUCTS.PUB \
| gpg --dearmor \
| sudo tee /usr/share/keyrings/oneapi-archive-keyring.gpg > /dev/null
RUN echo "deb [signed-by=/usr/share/keyrings/oneapi-archive-keyring.gpg] https://apt.repos.intel.com/oneapi all main" \
| sudo tee /etc/apt/sources.list.d/oneAPI.list
RUN apt update \
&& apt install -y intel-basekit
USER $USERNAME
以下のコマンドでビルドできる。
source /opt/intel/oneapi/setvars.sh
mkdir build-intel
cd build-intel
cmake .. -DLLAMA_BLAS=ON -DLLAMA_BLAS_VENDOR=Intel10_64lp -DCMAKE_C_COMPILER=icx -DCMAKE_CXX_COMPILER=icpx
cmake --build . --config Release