- はじめに
- 環境
- 準備
- llama.cppの動作確認
- llama.cppをサーバーとして実行
- Unityに必要なライブラリの追加
- レスポンスのクラスの作成
- Unityからllama.cppに対してAPIを叩く
- llama.cppのGPUオフロード対応
はじめに
llma.cppやStyleBertVITS2(音声合成ライブラリ) をunityで簡単に使えるように以下のライブラリを作りました! 記事の内容はライブラリ内に含まれていますので、記事の内容を実装される場合はライブラリをご使用ください
環境
- Unity 2023.2.4f1
- llama.cpp
準備
cmakeの環境作成
cmake公式から64ビットWindowsのインストーラをダウンロードして、インストールします
llama.cppの環境作成
まずはllama.cppをcloneします
git clone https://github.com/ggerganov/llama.cpp
次にcmakeを使ってllama.cppをビルドします
mkdir build cd build cmake .. -DLLAMA_CUBLAS=ON cmake --build . --config Release cd ..
モデルのダウンロード
今回は cyberagent/calm2-7b-chatのGGUFモデルを使うため、TheBloke/calm2-7B-chat-GGUFをダウンロードします。
こちらでは、calm2-7b-chat.Q4_K_M.ggufを使用しました
ダウンロードしたモデルを llama.cpp/models
に配置します
llama.cppの動作確認
現時点でllama.cppが単体で動くようになっているため、以下を実行してCPU推論ができることを確認します (cmakeターミナルが日本語だと文字化けするので、英語で質問しています)
./main.exe -m ./models/calm2-7b-chat.Q4_K_M.gguf --temp 0.1 -p "User:Please answer in Japanese. How tall is Mt. fuji:"
実行すると以下のようになると思います
system_info: n_threads = 12 / 24 | AVX = 1 | AVX_VNNI = 0 | AVX2 = 1 | AVX512 = 0 | AVX512_VBMI = 0 | AVX512_VNNI = 0 | FMA = 1 | NEON = 0 | ARM_FMA = 0 | F16C = 1 | FP16_VA = 0 | WASM_SIMD = 0 | BLAS = 0 | SSE3 = 1 | SSSE3 = 1 | VSX = 0 | sampling: repeat_last_n = 64, repeat_penalty = 1.100, frequency_penalty = 0.000, presence_penalty = 0.000 top_k = 40, tfs_z = 1.000, top_p = 0.950, min_p = 0.050, typical_p = 1.000, temp = 0.100 mirostat = 0, mirostat_lr = 0.100, mirostat_ent = 5.000 sampling order: CFG -> Penalties -> top_k -> tfs_z -> typical_p -> top_p -> min_p -> temp generate: n_ctx = 512, n_batch = 512, n_predict = -1, n_keep = 0 User:Please answer in Japanese. How tall is Mt. fuji: ASSISTANT: 富士山は、3776メートルです。 [end of text] llama_print_timings: load time = 1167.49 ms llama_print_timings: sample time = 4.14 ms / 18 runs ( 0.23 ms per token, 4348.88 tokens per second) llama_print_timings: prompt eval time = 541.57 ms / 15 tokens ( 36.10 ms per token, 27.70 tokens per second) llama_print_timings: eval time = 5475.73 ms / 17 runs ( 322.10 ms per token, 3.10 tokens per second) llama_print_timings: total time = 6057.06 ms Log end
llama.cppをサーバーとして実行
llama.cppをサーバーとして実行するときは、以下のコマンドを実行します
./server.exe -m models/calm2-7b-chat.Q4_K_M.gguf -c 2048 -t 24
オプションは以下になっています
- --threads N, -t N: 生成時に使用するスレッド数
- -c N, --ctx-size N: コンテキスト長
llama.cppのサーバモードは公式の ReadMeがあるので、詳細は以下をご確認ください。
Unityに必要なライブラリの追加
非同期処理に UniTaskやレスポンスの構造化を簡単にするために newtonsoft-jsonを使用します。
以下を manifest.json
に追加します
"com.unity.nuget.newtonsoft-json": "3.2.1", "com.cysharp.unitask": "2.5.0",
レスポンスのクラスの作成
llama.cppでcalm2にAPIをpostした際のレスポンスの中身を確認すると以下のようになっています。
{ "content": "鹿目まどかです。彼女はアニメのシリーズ全体を通じて、ファンに愛されています。\nASSISTANT: あなたは彼女の性格や外見だけでなく、彼女の内面にも深い魅力を感じているようです。彼女の絶望から立ち直る姿は多くの人々を感動させ、勇気を与えました。また、彼女は周囲の人々に対して優しく、思いやりのある行動をとることが多く、その人間性についても高く評価されています。", "generation_settings": { "frequency_penalty": 0.0, "grammar": "", "ignore_eos": false, "logit_bias": [], "min_p": 0.05000000074505806, "mirostat": 0, "mirostat_eta": 0.10000000149011612, "mirostat_tau": 5.0, "model": "models/calm2-7b-chat.Q4_K_M.gguf", "n_ctx": 2048, "n_keep": 0, "n_predict": 128, "n_probs": 0, "penalize_nl": true, "penalty_prompt_tokens": [], "presence_penalty": 0.0, "repeat_last_n": 64, "repeat_penalty": 1.100000023841858, "seed": 4294967295, "stop": [], "stream": false, "temperature": 0.800000011920929, "tfs_z": 1.0, "top_k": 40, "top_p": 0.949999988079071, "typical_p": 1.0, "use_penalty_prompt_tokens": false }, "model": "models/calm2-7b-chat.Q4_K_M.gguf", "prompt": "User:日本語で回答してください。まどマギで一番可愛いキャラは? Assistant: ", "slot_id": 0, "stop": true, "stopped_eos": true, "stopped_limit": false, "stopped_word": false, "stopping_word": "", "timings": { "predicted_ms": 59830.736, "predicted_n": 82, "predicted_per_second": 1.3705330317180122, "predicted_per_token_ms": 729.6431219512194, "prompt_ms": 1514.903, "prompt_n": 18, "prompt_per_second": 11.881948877254846, "prompt_per_token_ms": 84.16127777777778 }, "tokens_cached": 100, "tokens_evaluated": 18, "tokens_predicted": 82, "truncated": false }
上記をUnityで扱いやすくするためにクラス化します
using System.Collections.Generic; namespace AI { /// <summary> /// calm2のllama.cppのサーバーからのレスポンス /// </summary> public class Calm2Response { public string content; public GenerationSettings generation_settings; public string model; public string prompt; public int slot_id; public bool stop; public bool stopped_eos; public bool stopped_limit; public bool stopped_word; public string stopping_word; public Timings timings; public int tokens_cached; public int tokens_evaluated; public int tokens_predicted; public bool truncated; } public class GenerationSettings { public float frequency_penalty; public string grammar; public bool ignore_eos; public List<object> logit_bias; public float min_p; public int mirostat; public float mirostat_eta; public float mirostat_tau; public string model; public int n_ctx; public int n_keep; public int n_predict; public int n_probs; public bool penalize_nl; public List<object> penalty_prompt_tokens; public float presence_penalty; public int repeat_last_n; public float repeat_penalty; public ulong seed; public List<object> stop; public bool stream; public float temperature; public float tfs_z; public int top_k; public float top_p; public float typical_p; public bool use_penalty_prompt_tokens; } public class Timings { public double predicted_ms; public int predicted_n; public double predicted_per_second; public double predicted_per_token_ms; public double prompt_ms; public int prompt_n; public double prompt_per_second; public double prompt_per_token_ms; } }
Unityからllama.cppに対してAPIを叩く
最後にUnity側からllama.cppのローカルサーバーに APIを叩いていきます
Unityからllama.cppのローカルサーバーにpostするクラスは以下になります
using System.Threading; using Cysharp.Threading.Tasks; using UnityEngine; using UnityEngine.Networking; namespace AI { /// <summary> /// calm2-chatモデルを使用したチャット機能 /// </summary> public static class Calm2Model { /// <summary> /// llama.cppのサーバーURL /// </summary> private const string llamaServerURL = "http://localhost:8080/completion"; /// <summary> /// チャットを送信する /// </summary> /// <param name="prompt"></param> public static async UniTask<string> PostRequest(string prompt,CancellationToken cancellationToken) { // JSONデータの準備 var jsonData = $"{{\"prompt\": \"User:{prompt} Assistant: \",\"n_predict\": 128}}"; // UnityWebRequestオブジェクトの作成 using var webRequest = new UnityWebRequest(llamaServerURL, "POST"); // リクエストボディの設定 var jsonToSend = new System.Text.UTF8Encoding().GetBytes(jsonData); webRequest.uploadHandler = (UploadHandler)new UploadHandlerRaw(jsonToSend); webRequest.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer(); // ヘッダーの設定 webRequest.SetRequestHeader("Content-Type", "application/json"); // リクエストを送信し、完了を待つ await webRequest.SendWebRequest().ToUniTask(cancellationToken:cancellationToken); // エラーチェック if (webRequest.result != UnityWebRequest.Result.Success) { Debug.Log("Error: " + webRequest.error); return null; } // レスポンスの表示 var calm2Response = JsonUtility.FromJson<Calm2Response>(webRequest.downloadHandler.text); Debug.Log("Response: " + calm2Response.content); return calm2Response.content; } } }
後は適当に画面を作ってPost関数を作成して、Unity上で実行すると以下のようになります (UIは適当です)
llama.cppのGPUオフロード対応
llma.cppのGPUオフロードの動作確認
まずは何もせずに叩くとGPUオフロードができるかを試します
./main -m 'models/calm2-7b-chat.Q4_K_M.gguf' -n 100 --temp 0.1 -p 'User:Please answer in Japanese. How tall is Mt. fuji:' -ngl 32 -b 512
この状態で、BLAS = 1
になっていれば問題ありません。環境構築はスキップしてください
GPU版のビルド
以下を実行します
$ mkdir build $ cd build $ cmake .. -DLLAMA_CUBLAS=ON $ cmake --build . --config Release $ cd ..
実行後に llama.cpp/build/bin/Release/
に server.exe
と main.exe
が生成されているため、llama.cpp直下に移動させます
仮に生成されていない場合等は、以下の原因等が考えられるため確認してください
GPUオフロードのサーバーを実行する
以下でGPUオフロードするllama.cppのサーバーを立てることができます
.\server.exe -m .\models\calm2-7b-chat.Q4_K_M.gguf -ngl 32 -b 512