Dify/llama.cppの利用は盲目的にはできない件

Artificial Intelligence

llama.cppの心得:同時接続数に気を付けよう

Embeddingモデルを使う際、私はローカルSLMをよく利用します。理由は「レイテンシーが低いから」の1点に尽きます。API型LLMで実行する場合に比べて恐ろしく処理が速くなるんですね。また、API型モデルではその精度を追求するあまり、次元数が非常に大きくてデータが多く嵩んでしまう短所を持っています。

これに対してローカルSLMでは非常に次元数の小さな、それでいて長文トークンを収容でき、かつ性能の良いモデルが存在することで、これを活用してデータを小さくまとめることができるというのも一つ大きな理由になります。

しかし、碌な設定をせずにEmbeddingモデルを実装すると痛い目に遭います。私は ruri-v3-310mモデルをllama.cppを使用する形で以下のような構成を組んでました。

ExecStart=/opt/llama/bin/llama-server -m /opt/llama/models/ruri-v3-310m-Q8_0.gguf \
 --embedding --device CUDA1 --host 0.0.0.0 --port 8002

そして、PostgreSQL 15の英語ドキュメントを丸っとDifyナレッジに搭載しようとしましたが、以下のようなtop画面出力を最後にサーバから応答が返ってこなくなりました・・・

top - 07:18:41 up  4:14,  2 users,  load average: 3.91, 2.89, 1.64
Tasks: 623 total,   4 running, 618 sleeping,   0 stopped,   1 zombie
%Cpu(s):  9.5 us,  4.2 sy,  0.0 ni, 84.0 id,  2.1 wa,  0.0 hi,  0.2 si,  0.0 st 
MiB Mem :  15899.2 total,    257.3 free,  13691.8 used,   3227.7 buff/cache     
MiB Swap:   4096.0 total,      1.1 free,   4094.9 used.   2207.5 avail Mem 


    945 aiuser    20   0   51.1g   6.4g 366504 R 149.8  41.1   8:55.85 llama-server                                                                                                                                                          
  94660 root      20   0 2126244 640848  51264 S  67.0   3.9   2:26.51 weaviate                                                                                                                                                              
  95818 1001      20   0 5612152 558864  19464 S  34.7   3.4   4:10.55 celery       

何が起きたのか

Dify及びllama.cppは、デフォルト値として制限なし(0だったり-1だったり)を設定してることが多いです。これは、要求された処理をより高速に処理することが狙いにあるのだろうと推測されます。可能な限り並行処理数を向上させ、一気に処理しようとするのだと思われます。

しかし、私の環境はメモリが16GBしか搭載されていません。
そのことから、際限なく同時処理数を引き上げようとした結果、メモリ消費が一気に増大し、結果としてメインメモリが枯渇したものと推察されます。
41.1%という値は、「メインメモリの41.1%を消費したい」というllama.cppの叫びだったということになりますね。Embeddingモデルを動かすために8GB近くもメモリ頂戴だなんて・・・我が家の環境は貧乏でなぁ・・・それはさすがにできませんのですじゃOrz

同時接続数を制御するコマンドライン

そこで、私はembeddingモデルとRerankerモデルに以下のような引数を追加しています。

ExecStart=/opt/llama/bin/llama-server -m /opt/llama/models/ruri-v3-310m-Q8_0.gguf \
 -np 10 -c 81920 -b 8192 -ub 8192 \
 --embedding --device CUDA1 --host 0.0.0.0 --port 8002

追加した引数の説明について、以下の通りです。

引数説明
-np並行処理数を指します。今回は10とし、最大10個のリクエストに対応できるよう構成します。
-c最大コンテキスト長を指定します。単位はトークンです。
モデルコンテキスト長×並行処理数
となる値を指定します。
-bバッチサイズを指定します。今回のケースでは、ruri-v3-310mモデルの最大コンテキスト数を指定します。
-ubバッチサイズの厳密な上限を指定します。今回のケースでは、ruri-v3-310mモデルの最大コンテキスト数を指定します。

このように設定することで、過剰なリクエストを受け入れずに待機させることが可能になります。受け入れられなかったリクエストは待機キューに回され、以降順次処理されるように動きが変わります。

同時接続数を修正した結果

さて、動きはどのように変わったでしょうか。その様子を見て見ることにします。

top画面では以下のようになりました。

上記、プロセス番号70124が ruri-v3-310m が動作しているプロセスになっていますが、メモリ消費の割合が7.7%でとどまっていました。これ以上にメモリ消費をすることは最後までありませんでした。どうやら、同時接続数の設定が効いているようです。

続いてnvidia-smiも併せて見てみます。以前ですとEmbeddingモデルやRerankerモデルを動かしてるGTXのメモリ消費は720MiBしかなかったのですが、それより多めにメモリが消費されていることが分かります。

Mon May 18 01:28:57 2026
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 590.48.01              Driver Version: 590.48.01      CUDA Version: 13.1     |
+-----------------------------------------+------------------------+----------------------+
| 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. |
|=========================================+========================+======================|
|   1  NVIDIA GeForce GTX 1660 ...    On  |   00000000:03:00.0 Off |                  N/A |
| 66%   73C    P2             63W /  125W |    2050MiB /   6144MiB |     61%      Default |
|                                         |                        |                  N/A |
+-----------------------------------------+------------------------+----------------------+

+-----------------------------------------------------------------------------------------+
| Processes:                                                                              |
|  GPU   GI   CI              PID   Type   Process name                        GPU Memory |
|        ID   ID                                                               Usage      |
|=========================================================================================|
|    1   N/A  N/A           68683      C   /opt/llama/bin/llama-server             916MiB |
|    1   N/A  N/A           70124      C   /opt/llama/bin/llama-server            1128MiB |
+-----------------------------------------------------------------------------------------+

GPUもそれなりに使われているようで61%と表示されていますね。Turing(TensorCoreなし)コアとは言え、Embeddingモデルの処理は非常に高速です。

こうして無事、ナレッジ中3121個のチャンクは無事に処理されたのであります。めでたしめでたし。

くれぐれも設定は計画的に・・・

私自身舐めてた話ではありますが、それなりの性能を持つ環境下であればこのような設定をせずともリソース枯渇が生じる前に処理が終わり、特に問題ない結果になったかもしれません。しかし、今の私のような、切り詰めて買ったようなパーツの寄せ集めで動作する環境下ですと、このようにインストールしてポン!みたいな使い方ってなかなか難しいのだろうと思います。

こういうことを想定し、構成されるコンポーネントでどのような設定が必要かは押さえておいたほうが良いというのが私の考えでして、このあたりはITインフラエンジニア側の思想に若干似ているんじゃないかという風に思います。

現在llama.cppを使ったエンジンがChat/Embedding/Rerankの3種類あり、それを土台としてDify, Langfuse, LiteLLMが動作しており、それらはすべて1台の旧型WSであるZ440上で動作しています。16GBしかない環境ではありますが、何がどの程度リソース消費をするのかをある程度予測することで、貧弱な環境でも一定の体験はでき、そのうえで実際の本番環境なり、会社の環境なりで本格的な展開に臨めるようにできるというのは、ある意味プロダクト知見をいち早く押さえていくための効率的な手段だと考えているところです。

どうしてもプロダクトの表面部分、実際の操作部分に目が行きがちですが、色々少しだけ深めに視野を広げていくことも、必要なことではないかなと感じる今日この頃です。私もかなりいろんなことを忘却してしまっているので、ノウハウを増やしていかねばです。ではでは。

コメント

タイトルとURLをコピーしました