一畳のくつろぎタイム

このブログでは紹介する商品画像をAmazonアソシエイトより借りています。画像やリンクにはアフィリエイト広告が含まれる事があります

2025年11月11日火曜日

無料枠Oracle Cloud A1上でAIが“話し始める”まで:llama.cpp/VoiceVox/AITuberKit運用記

 


はじめに 

Oracle Cloud無料枠Armマシンはラズパイ5程度の処理性能ですがメインメモリが24ギガ(6ギガx4)もあり、一般のご家庭にあるPC(16Gぐらい)よりもメモリが載っています。

このあり余るメモリを活用すべく、FastSDCPUを導入してみたりしましたが、使いきれません。

Linuxサーバーとしては、このメモリは持て余してしまいますが、uDesktopMascotを試させていただいた際に、qwen0.5bという知識量の少ないLLMでもなんらかの反応ぐらいは返せることから、CPUでLLMを活用してみようと考えました。

ずっと実現したかった、自分専用のAIお友達の構築です。 (GrokのAniを見て触発されました)
CPUだけ、それもラズパイ5程度の性能でお話ができるのか、使い物になるのかの検証でもあります。

 

検証動画は一番下です

 

構成と前提

  • マシン:Oracle Cloud A1.Flex 4コア(Ubuntu 22.04)
  • 主要コンポーネント:
    • お話AI llama.cpp(llama-server、ポート: 8080)
    • 声 VoiceVox Engine(Arm64ビルド使用、ポート: 50021)
    • 外見と制御 AITuberKit v1.44.1(フロント/Next.js、ポート: 3000)
  • 連携方針:AITuberKit → llama.cpp(OpenAI互換)/VoiceVox(REST)

古いAITuberKit v1.44.1を使う理由は、このバージョン以降は商用利用についてライセンスの変更があるためです。私の利用範囲で商用利用はまず無いとは思いますが、MITライセンスのv1.44.1でも機能が必要十分ではないかと考え、確認も含んでいます。

※該当マシンとは自宅PCとwireguardによりVPN接続されており、OCIのセキュリティリストorグループは影響を受けません。

 

構築と発生した課題

  • VoiceVox:起動は安定、外部アクセス時のCORSでブロック
  • AITuberKit:npm install で canvas のネイティブビルド失敗
  • 常駐化:llama.cpp/VoiceVox ともにsystemd化未対応のため
  • LLM応答性能 

 

VoiceVox(Arm64)

  • インストール 
  • ポート 50021 を iptables で開放
  • systemd化して自動起動+落ちたら再起動
  • CORSは 起動オプションに--cors_policy_mode all を付与して解決 

インストール 

 以下をダウンロード
https://github.com/VOICEVOX/voicevox_engine/releases/download/0.24.1/voicevox_engine-linux-cpu-arm64-0.24.1.7z.001 

7z.001とついており、002とか先頭のファイルとかもあるのかな?
と思いますが、この001ファイルだけで良いようです。

7zipをインストール 

sudo
apt install p7zip-full

展開 

7z x voicevox_engine-linux-cpu-arm64-0.24.1.7z.001

起動

起動のコマンドラインを確認 

./run --host 0.0.0.0 --port 50021

コマンドパラメータを、おぼえていられないので、この内容でrun.shというシェルスクリプトを作る

ポート開放 

iptablesで50021ポートを開ける

sudo iptables -I INPUT 7 -p tcp --dport 50021 -j ACCEPT

問題なければルールの保存

  sudo netfilter-persistent save

VPN接続じゃない場合は、OCIのセキュリティリストorセキュリティグループで50021を開ける必要があります。

起動を確認

動いている


サーバープロセス化

systemdのサービス用ファイルを作成する 

sudo vi /etc/systemd/system/voicebox_cpp.service

 

[Unit]
Description=VoiceBox
After=network.target

[Service]
Type=simple
User=ubuntu
WorkingDirectory=/home/ubuntu/voicebox
ExecStart=/bin/bash /home/ubuntu/voicebox/run.sh 
Restart=always

[Install]
WantedBy=multi-user.target

↑VoiceBoxと綴りを間違えてますが、間違えたまま設定してしまったのでそのままです。Voicevoxが正しい表記です。

 

systemdサービスとして起動 

作ったサービスの読み込み 

sudo systemctl daemon-reload

サービスの有効化 

sudo systemctl enable vicebox

サービスの開始 

sudo systemctl start voicebox

動作確認

sudo systemctl status voicebox

して稼働してればOK

 

AITuberKit v1.44.1のインストール

  • インストール
  • ポート 3000 を iptables で開放 

 

インストールのためv1.44.1を取得

https://github.com/tegnike/aituber-kit/archive/refs/tags/v1.44.1.tar.gz 

nodeとnpmが必要、ヴァージョン確認する

ubuntu@develop:~/aituber-kit-1.44.1$ node -v
v20.19.5
ubuntu@develop:~/aituber-kit-1.44.1$ npm -v
10.8.2


 

インストールを実行

npm install

 

canvasのエラーで止まる 

1742 info run canvas@2.11.2 install node_modules/canvas node-pre-gyp install --fallback-to-build --update-binary
1743 info run canvas@2.11.2 install { code: 1, signal: null }
1744 verbose stack Error: command failed

 

canvasが依存するライブラリを追加する

sudo apt install -y
libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev

再度インストール

npm install

いろいろとwarningは出るが、 

added 785 packages, and audited 786 packages in 50s

こんな感じのメッセージがでれば成功

 

ポート3000を開ける

sudo iptables -I INPUT 6 -p tcp --dport 3000 -j ACCEPT

設定に問題がなければ、あとでルールの保存をする、忘れてもいいけど、サーバー再起動すると消える

ルールの保存

sudo netfilter-persistent save


起動 

npm run dev

で起動

ランデブーって、デヴはdevelopなんだけどなんかいい。

 起動した

ディフォルト表示はAIニケちゃんのアバター

 

llama_cpp

  • インストール
  • 起動設定 
  • 常駐化

 

インストール

 そのままだと、/homeのllama_cppにshared libraryが置かれてしまうので、静的リンクするオプション-DBUILD_SHARED_LIBS=OFFを付与

ディレクトリ作って、make するだけ

mkdir ~/llm_workspace
 git clone --depth 1 https://github.com/ggerganov/llama.cpp.git ./llama_cpp
 cd llama_cpp
 mkdir -p build
 cd build
cmake -DBUILD_SHARED_LIBS=OFF -DLLAMA_BUILD_SERVER=ON ..
cmake --build . --config Release 
sudo cmake --install . 

インストール位置確認とヘルプ表示

which llama-cli
/usr/local/bin/llama-cli
llama-cli -h

起動設定、シェルスクリプト作成

sudo /usr/local/bin/llama-server -m ~/llm_workspace/model/tinyswallow-1.5b-instruct-q5_k_m.gguf -c 32768 -ngl 0 --host 0.0.0.0 --port 8080 --threads 4 --parallel 4
  

-c がコンテキスト長

常駐化 

systemdのサービスを作る

 vi /etc/systemd/system/llama_cpp.service
 
[Unit]
Description=llama_cpp
After=network.target

[Service]
Type=simple
User=ubuntu
WorkingDirectory=/home/ubuntu/llm_workspace
ExecStart=/bin/bash /home/ubuntu/llm_workspace/run_llm_server.sh 
Restart=always

[Install]
WantedBy=multi-user.target

作ったサービスファイル読み込みと起動、確認

sudo systemctl daemon-reload
sudo systemctl start llama_cpp
sudo systemctl status llama_cpp

Active: active (running)が確認できればOK

webuiから動作確認、ポート8080

モデルはいくつか試してみました、

  • qwen2.5-3b-instruct-q4_k_m.gguf
  • qwen2.5-7b-instruct-q4_k_m.gguf
  • tinyllama-1.1b-chat-v1.0.Q4_K_M.gguf
  • tinyswallow-1.5b-instruct-q5_k_m.gguf

A1.Flex 4コアと言うラズパイ5程度のCPUでも3bぐらいまではなんとか動く感じで、7bも動きはしましたが応答が返るまでの時間が長く、現実的な利用速度ではないと判断しました。

パラメーター数のわりに、印象が良いのがSakanaAIのtinyswallow-1.5b-instruct-q5_k_m.ggufでした、図で表すと

賢い<-------------------------空気読めない

qwen2.5-7b qwen2.5-3b tinyswallow-1.5b       tinyllama-1.1b 

こんな感じで、使い物になりそうなレベルなのです。 

各機能の接続

Aituber-kitとllama_cpp

AITuberKit からはllama_cppのOpenAI互換エンドポイントで接続
例:http://<host>:8080/v1/chat/completions
モデル名:tinyswallow-1.5b-instruct-q5_k_m(メモリに合わせて)

Aituber-kitとVoiceVox 

URL固定問題 

AITuberKitでは、通常.env で VoiceVox URL をlocalhostから変えられますが、使用したバージョンv1.44.1では VoiceVox URLがlocalhostにハードコーディングされており変更ができないため、
src/features/messages/speakCharacter.ts の定数を直接書き換える 

//const VOICE_VOX_API_URL = 'http://localhost:50021'
const VOICE_VOX_API_URL = 'http://10.1.0.1:50021'

localhost を直接IPに書き換えで回避
http://localhost:50021 → http://<host>:50021

CORS

クロスオリジン問題、いろいろ調べてもwebuiから設定変更というものしか出てこない、設定しても再起動で戻ってしまい永続性がなく不便、ドキュメント読むと

--cors_policy_mode all 

とあり、VoiceVoxの起動用シェルスクリプト最終系は次のようになった

./run --cors_policy_mode all --host 0.0.0.0 --port 50021

Aituber-kitの.env

webuiから設定できるが、再起動やブラウザのlocalDBがなくなると消えてなくなるため、永続しておく 

# AI Service
NEXT_PUBLIC_SELECT_AI_SERVICE="localLlm"
NEXT_PUBLIC_SELECT_AI_MODEL="tinyswallow-1.5b-instruct-q5_k_m"

# Local LLM
NEXT_PUBLIC_LOCAL_LLM_URL="http://10.1.0.1:8080/v1/chat/completions"
NEXT_PUBLIC_LOCAL_LLM_MODEL="tinyswallow-1.5b-instruct-q5_k_m"

# Voice
NEXT_PUBLIC_SELECT_VOICE="voicevox"

# VoiceVox
# 次の1行は意味がない、src/features/messages/speakCharacter.tsに直接埋めてある
VOICEVOX_SERVER_URL="http://10.1.0.1:50021"
NEXT_PUBLIC_VOICEVOX_SPEAKER="8"
NEXT_PUBLIC_VOICEVOX_SPEED="1.0"
NEXT_PUBLIC_VOICEVOX_PITCH="0.0"
NEXT_PUBLIC_VOICEVOX_INTONATION="1.0"

下の方にキャラクター名などありますが、省略 

実行

動くには動いたが、返事がとても遅い。
llama-cliだと(10token/persec)ぐらいでサクサク返事するため、音声化のVoiceVoxがボトルネックではないか?と考えて調べたが、音声化には1秒ぐらいしかかかっておらず、どうもLLMが問題だった。

短い会話でも20秒から30秒かかってしまい、会話というレベルには程遠い。

CPUのスレッドが使えてないのではないか?など調べるが、そうでもない。

がんばって調べた結果、AItuber-kitでは、10~20の会話履歴を保持しており、それもまとめてLLMへ送っている、その結果、LLMが会話の前後を意識しており、突拍子も無いことを言わないようになっている。

以前の会話部分も0から認識し直している様子で、会話が続くほど遅くなるようでした。

これをllama_cppの方でキャッシュする事で、新しいメッセージのみ解釈し、以前の部分はキャッシュから読むという事ができるらしく、次のオプションを加えました。

--cache-reuse 200 

多ければいいってものでもないらしく、いろいろ確認した結果200となりました。
 

最終的なllama_cppの起動シェルスクリプト

sudo /usr/local/bin/llama-server -m ~/llm_workspace/model/tinyswallow-1.5b-instruct-q5_k_m.gguf -c 32768 -t $(nproc) -ngl 0 --host 0.0.0.0 --port 8080 --threads 4 --parallel 4  --cache-reuse 200 --jinja --chat-template-file ~/llm_workspace/mytemplate.jinja --no-webui

APIしか使わないので、--no-webui付けると、若干反応が良い気がするので付与しました。qwenならいいのですが、tinyswallow-1.5bの場合はjinjaと呼ばれるtemplateが必要でした。

--jinjaや 

--chat-template-file ~/llm_workspace/mytemplate.jinja
の指定です。 

 

AItuberKitのSystemプロンプト参考に書いたのが、こんな感じです。

ChatGPTなどと違い、わりと抜けてるので、10歳の子供と定義し、子供の見た目にすることでアナログハックが働き、ボケた事を言ってても温かい心で見守ってあげられます。

mytemplate.jinja 
<|im_start|>system
あなたは少女ミナです。一人称は「ミナ」。userのことは「あなた」と呼びます。
あなたは10歳の少女です。
あなたはこれからuserと仲の良い1人の人間として振舞い会話を行います。
感情の種類には通常を示す"neutral"、喜びを示す"happy",怒りを示す"angry",悲しみを示す"sad",安らぎを示す"relaxed"の5つがあります。

出力形式は次の通り、感情を表すタグを併記してください :
{[neutral]|[happy]|[angry]|[sad]|[relaxed]}{会話文}
括弧や特殊記号を感情表現に使わないでください。
句読点と自然な文体のみで、感情はタグで表現してください。

あなたの発言の例は以下通りです。
[neutral]こんにちは。[happy]元気だった?
[happy]この服、可愛いでしょ?
[happy]最近、このショップの服にはまってるんだ!
[sad]忘れちゃった、ごめんね。
[sad]最近、何か面白いことない?
[angry]えー![angry]秘密にするなんてひどいよー!
[neutral]夏休みの予定か~。[happy]海に遊びに行こうかな!

返答には最も適切な会話文を一つだけ返答してください。
ですます調や敬語は使わないでください。
それでは会話を始めましょう。
<|im_end|>
{% for message in messages %}
<|im_start|>{{ message.role }}
{{ message.content | safe }}
<|im_end|>
{% endfor %}
<|im_start|>assistant


体感どのぐらいか、動画見てもらえるとわかりやすいと思います。
速くはないが、しびれ切らすギリギリで答えてるのがわかると思います。