【LLM】CPU環境でLlama-3-ELYZA-JP on gRPCを試してみた
はじめに
こんにちは。 @yu_yukk_y です!
さて、昨今LLMをベースとしたAI機能をアプリに組み込む会社・開発者が増えています。現状LLMをアプリに組み込む場合は
- API経由で利用する(GPT-4o, Gemini-1.5-Proなど)
- GPUが使用可能なサーバーにLLMを組み込んだアプリケーションをデプロイする
- modalのような格安なGPUコンピューティングリソースサービスを用いる
の3つの手段が考えられますが、1・2はどちらの手段を取ってもランニングコストはそこそこな金額になってしまいますし、3のような独立したクラウドサービスはアプリケーションの要件次第では採用できないこともしばしばあります[1]。
CPU環境でLLMが動かせればもっと用途の幅が広がるのに。。。と思っていた矢先、CPU環境でもLLMが動かせるようになるというllama.cpp
の存在を知りました。
また、このllama.cpp
をlangchain
から呼び出すことで、Pythonやlangchainの柔軟な機能と組み合わせてアプリケーションを作れることを知りました。
今回はllama.cpp
を用いて作成したgRPCサーバーのコードと、作ってみて得られた気づきについて書きます。
前提知識
Llama-3-ELYZA-JPとは
Llama-3-ELYZA-JPとは、東大松尾研究室発のAIカンパニー「ELYZA」が開発した日本語大規模言語モデルのことです。
この記事で使用したモデルLlama-3-ELYZA-JP-8B
は、meta社が開発したモデルMeta-Llama-3-8B-Instruc
に対し、日本語における指示追従能力を拡張するための、日本語追加事前学習および事後学習を行ったものです。
この記事では後述するllama.cpp
で利用するために、このモデルの量子化バージョンであるLlama-3-ELYZA-JP-GGUF
を使用しています。
また、こちらのモデルはMETA LLAMA 3 COMMUNITY LICENSEに準拠しています。使用する前にライセンスの内容を読み込んでから使用してください。
llama.cppとは
llama.cppとは、LLMに量子化技術を施すことで推論の高速化を図るツールです。これを用いることでローカルPCでも現実的な速度でLLMを利用することができます。
こちらについてはサイバーエージェントさんの技術記事にまとまっているため、より詳しくはそちらをご覧ください。
langchainとは
LangChain is a framework to build with LLMs by chaining interoperable components. LangGraph is the framework for building controllable agentic workflows.
(deepL訳)LangChainは、相互運用可能なコンポーネントを連結してLLMを構築するフレームワークです。 LangGraphは、制御可能なエージェントのワークフローを構築するためのフレームワークです。
公式にもこのように書かれているように、langchainはLLMモデルを組み合わせたり、ワークフローを作成したり、またRAGのようなLLMをより強力にする手法を実装しやすくしてくれるフレームワークです。
色々な機能が存在するのですが、今回はllama.cppをPythonから呼び出すためだけに使用します[2]。
gRPCとは
gRPC is a modern open source high performance Remote Procedure Call (RPC) framework that can run in any environment. It can efficiently connect services in and across data centers with pluggable support for load balancing, tracing, health checking and authentication. It is also applicable in last mile of distributed computing to connect devices, mobile applications and browsers to backend services.
(deepL訳)gRPCは、どのような環境でも実行できる最新のオープンソース高性能リモートプロシージャコール(RPC)フレームワークです。 ロードバランシング、トレース、ヘルスチェック、認証をプラグインでサポートし、データセンター内やデータセンター間のサービスを効率的に接続することができます。 また、デバイス、モバイル・アプリケーション、ブラウザをバックエンド・サービスに接続する分散コンピューティングのラストワンマイルにも適用できる。
公式に書かれている通り、gRPCはさまざまな環境で実行可能で高性能なRPCプロトコルを採用したフレームワークです。マイクロサービス間の通信などにしばしば用いられています。
can run any environment
と書かれていますが、実行する際はHTTP/2に対応した環境が必要です。
開発の際は.proto
ファイルにProtocol Buffersを使ったサービスの定義をかき、それをもとにさまざまなプログラミング言語のソースコードを自動生成して開発します。
こちらについてはさくらインターネットさんの技術記事にまとまっているため、より詳しくはそちらをご覧ください。
ソースコード
ソースコードは下記のものになっています。
実行する際はREADMEにしたがって環境を準備してください。
src/guide/guide.py
src/guide/guide.py
で実際にllama.cpp経由でLlama-3-ELYZA-JPを呼び出しています。
import multiprocessing
from langchain_community.chat_models import ChatLlamaCpp
llm = ChatLlamaCpp(
temperature=0.1,
model_path="./src/guide/Llama-3-ELYZA-JP-8B-q4_k_m.gguf",
n_threads=multiprocessing.cpu_count() - 1,
repeat_penalty=1.5,
verbose=True,
)
def think_response_msg(request_msg):
messages = [
(
"system",
"あなたは国語の先生です。送られてきた文章に対して、適切な返答をしてください。なお、返事は日本語でお願いします。",
),
("human", request_msg),
]
return llm.invoke(messages).content
langchain(ver 0.3)ではlangchain_community.chat_models
経由でllama.cppの機能を呼び出すことができます。
ChatLlamaCppメソッドには多くの設定項目がありますが、調査のためここでは最低限しか設定していません。また、GPUを利用しないために一部の設定をあえて省いています。
設定次第でGPU & CPUを両方使用可能にし、よりパフォーマンスを高めることもできます。実行環境に余裕がある際は検討してみてください。
なお、全ての設定値はlangchainのドキュメントから確認できます。
think_response_msg関数ではLLMに「あなたは国語の先生です。送られてきた文章に対して、適切な返答をしてください。なお、返事は日本語でお願いします。」という役割を与えて、ユーザーから送られてきたメッセージ(request_msg)に対する返事を生成してもらっています。
src/proto/guide.proto
ここではgRPCの定義をProtocol Bufferを用いて記述しています。
syntax = "proto3";
option java_multiple_files = true;
option java_package = "dev.yukky-sandbox.yukky-sandbox-guide-server.guide";
option java_outer_classname = "GuideProto";
option objc_class_prefix = "GID";
package guide;
// ゆっきーの砂場の案内をするAI機能の定義
service Guide {
// ユーザーからのメッセージを受けてレスポンス内容を考える
rpc ThinkResponseMsg (UserMsg) returns (GuideMsg) {}
}
// ユーザーからのメッセージ
message UserMsg {
string msg = 1;
}
// AIのメッセージ
message GuideMsg {
string msg = 1;
}
今回はmsg
というキーでユーザーからのテキストを受け取り、msgというキーでAIからのメッセージをクライアントに返すように定義しています。
また、通信のやり取りはシンプルなUnary RPCを採用しています。
src/server/servicer.py
servicer.py
では実際に上記の関数を呼び出すメソッドと、gRPCに渡すServicerクラスを作成しています。
from proto import guide_pb2
from proto import guide_pb2_grpc
from guide import guide
class GuideServicer(guide_pb2_grpc.GuideServicer):
def ThinkResponseMsg(self, request, context):
response_msg = guide.think_response_msg(request.msg)
return guide_pb2.GuideMsg(msg="Hello, {}".format(response_msg))
実際に動かしてみて
まず、返答内容については「まずまず?」といった印象を受けました。日本語モデルと言いつつもベースがllamaなこともあり、指定しないと英語で返ってきます。
こちらは自分の用意したプロンプトの問題もありそうですし、調整次第では改善が可能かもしれません。
ローカル環境で動作させられるため、いくら調整しようがレート制限にかからないですし、お金もかからないのが最高ですね!!
また、LLMをCPUのみの環境で動かせるようになったことには感動しました。
(画像はメモリ16GBのApple M2 Proでの実行結果)
従来重いモデルを動かす際は、処理が1日では終わらなかったり、そもそもPCからすごい音がしだしたり。。なんてもことが多かったように思いますが、LLMのような重量なモデルを数分で動かせるようになったのは本当にすごいことです。
一方で、今回はCPUのみを搭載したサーバー環境で動作させて、実際のアプリケーションとして利用できないか?といった観点での検証を行っていたのですが、CPUのみの環境でアプリケーションとして動作させるにはまだスピードが追いついてなさそうな印象を受けました。実運用を考える際はまだGPUとセットで考える必要がありそうです。
Discussion