
PrismMLのBonsai 2をGithub Actionsで動かしてみる
こんばんは、情報システム室の夏目です。
先日PrismMLが新しい超低ビットオープンウェイトモデルのBonsai 2を公開しました。
軽量モデルではスマホ端末での使用も可能で、iPhone 18 ProにおいてBonsai 2 9Bでは最大 34 tokens/sec 、Bonsai 2 2Bでは最大 120 tokens/sec の処理速度が出るそうです。
スマホでも動くモデルということで、今回はこのモデルをGithub Actions上で動かそうと思います。
Bonsai 2
PrismMLが開発している超低ビットのオープンウェイトLLMです。
Qwen系をベースに重みを超低ビットにすることで軽量にして、高速化と省電力化を目指しています。
第一世代では1bitか {-1, 0, +1} の3値表現の超低ビットでしたが、第二世代であるBonsai 2では3値表現のみとなっています。
また、第二世代では入力にテキストと画像を使用することができます。
Bonsai 2のパラメータ数は 2B/4B/9B/27B の4種があり、 2B/4B/9B を小型モデルとされています。
(小型モデルはQwen3.5を、27BはQwen3.8をベースにしています)
対応プラットフォームはllama.cpp経由でのNVIDIA GPU上とMLX経由でAppleデバイス (Mac, iPhone, iPad)上で動作すると書かれていました。
Github Actions上で動かしてみる
CPUでも動かすことができるので、Github Actions上で動かして見ます。
2B/4B/9B/27B 全部を動かしてみます。
ワークフロー定義
Bonsai 2を動かすためには PrismML-Eng/llama.cppフォークを使用する必要があります。
標準のllama.cppでは動きません。
今回4つのモデルを動かすので、workflow_callトリガーを使って試す部分を使い回す形でワークフローを作成します。
on:
workflow_call:
inputs:
model_url:
required: true
type: string
projector_model_url:
required: true
type: string
env:
BIN_URL: "https://github.com/PrismML-Eng/llama.cpp/releases/download/prism-b10777-e3de715/llama-prism-b10777-e3de715-bin-ubuntu-x64.tar.gz"
BIN_FILE_NAME: "data.tar.gz"
jobs:
exec:
runs-on: ubuntu-24.04
steps:
- name: Download BIN
run: |
set -xeuo pipefail
curl "${{ env.BIN_URL }}" -o "${{ env.BIN_FILE_NAME }}" -L
- name: Download Model
run: |
set -xeuo pipefail
curl "${{ inputs.model_url }}" -o "${{ inputs.model_name }}" -L
- name: Download Projector Model
run: |
set -xeuo pipefail
curl "${{ inputs.projector_model_url }}" -o "${{ inputs.projector_model_name }}" -L
- name: Unarchive
run: |
set -xeuo pipefail
mkdir bin
tar -xzf "${{ env.BIN_FILE_NAME }}" -C bin --strip-components=1
- name: Start Server
id: server
background: true
run: |
set -xeuo pipefail
./bin/llama-server \
-m "./${{ inputs.model_name }}" \
--mmproj "./${{ inputs.projector_model_name }}" \
-ngl 0 \
-fa on \
-c 32768 \
--port 8080
- name: wait
run: |
set -xeuo pipefail
for i in {1..120}; do
if curl -fsS http://localhost:8080/health; then
exit 0
fi
echo -n "$i, "
sleep 1
done
exit 1
- name: call
run: |
set -xeuo pipefail
curl http://localhost:8080/v1/chat/completions -H "Content-Type: application/json" -d '{
"messages": [{"role": "user", "content": [
{"type": "text", "text": "空がなぜ青いのか三行で教えて?"}
]}],
"chat_template_kwargs": {"enable_thinking": false}
}'
- name: Stop server
cancel: server
workflow_callトリガーのジョブのワークフロー定義です。
inputsで使用するモデルとプロジェクターモデルのURLを渡します。
Start server ステップにおいてllama.cppのOpenAI互換APIとしてバックグラウンドで動かし、 call ジョブでcurlで叩く構成にしています。
(llama-cliで試した時にはなぜか数分待っても実行がとまらなかったので)
Stop server ステップにおいて Start server ステップでバックグラウンドで動かしているseverを止めています。
止めないといつまでもワークフローが動き続けてしまうので。
serverを起動するためのコマンドやcurlでAPIを叩く部分はモデルカードにあったものをほぼそのまま使っています。
GPUを使わないようにする設定やプロンプトを 空がなぜ青いのか三行で教えて? に変えた程度です。
exec.ymlを定義したので、これを呼び出すワークフローを書いていきます。
on:
push:
jobs:
exec:
name: Exec
uses: ./.github/workflows/exec.yml
secrets: inherit
with:
model_url: "https://huggingface.co/prism-ml/Ternary-Bonsai-2-2B-gguf/resolve/main/Ternary-Bonsai-2-2B-PQ2_0.gguf?download=true"
projector_model_url: "https://huggingface.co/prism-ml/Ternary-Bonsai-2-2B-gguf/resolve/main/Ternary-Bonsai-2-2B-mmproj-Q8_0.gguf?download=true"
on:
push:
jobs:
exec:
name: Exec
uses: ./.github/workflows/exec.yml
secrets: inherit
with:
model_url: "https://huggingface.co/prism-ml/Ternary-Bonsai-2-4B-gguf/resolve/main/Ternary-Bonsai-2-4B-PQ2_0.gguf?download=true"
projector_model_url: "https://huggingface.co/prism-ml/Ternary-Bonsai-2-4B-gguf/resolve/main/Ternary-Bonsai-2-4B-mmproj-Q8_0.gguf?download=true"
on:
push:
jobs:
exec:
name: Exec
uses: ./.github/workflows/exec.yml
secrets: inherit
with:
model_url: "https://huggingface.co/prism-ml/Ternary-Bonsai-2-9B-gguf/resolve/main/Ternary-Bonsai-2-9B-PQ2_0.gguf?download=true"
projector_model_url: "https://huggingface.co/prism-ml/Ternary-Bonsai-2-9B-gguf/resolve/main/Ternary-Bonsai-2-9B-mmproj-Q8_0.gguf?download=true"
on:
push:
jobs:
exec:
name: Exec
uses: ./.github/workflows/exec.yml
secrets: inherit
with:
model_url: "https://huggingface.co/prism-ml/Ternary-Bonsai-2-27B-gguf/resolve/main/Ternary-Bonsai-2-27B-PQ2_0.gguf?download=true"
projector_model_url: "https://huggingface.co/prism-ml/Ternary-Bonsai-2-27B-gguf/resolve/main/Ternary-Bonsai-2-27B-mmproj-Q8_0.gguf?download=true"
動かしてみる
ワークフローの定義ができたので動かしてみます。




全部動きました。
また、27Bのモデルを使用しても1分弱で回答しています。
色々作業をさせるのにも使えそうです。
ちなみに回答は次のようになっていました。
空の色は、光が空を通って色として現れる現象に起因します。この現象は主に以下の通り説明できます。
1. **光波の色**: 空中の光は、すべてを赤を最も長い波長に広がり、紫外線を長くし、遠くから視認できない青の光を生成します。この「赤の光」の長さが空の色を決定的に捉えることができます。
2. **観測の距離**: 遠くから空を見ていると、遠端の赤い光をより鮮明に見えるため、空が赤いように見える現象です。
3. **気体の温度影響**: 暖かい空(例えば夏や陽午)では、水蒸気などが増加するが、この影響は赤い色ではなく、青く見えるものではありません。
結論として、空が青いのは**「赤い光の長さが赤い色より短く」**という物理的な関係にあります。
空が青いのは、地球からの陽光が大気中での水素分子などに散乱して、私たちがその光を見ることがからなるからです。
空の色は、大気の成分である窒素と酸素の粒子が太陽の光を散らかすことで生まれます。
赤や青など色分けされる現象は、光の波長によって粒子との衝突の仕組みが異なるためです。
波長の短い青い光よりも赤い光の方が長いため、青い色が見えます。
太陽光が大気中のガスや塵に衝突して、他の色と比べて波長の短い光が最も強く散乱されるためです。
この現象を「レイリー散乱」と呼び、その結果、空全体が青く見えることになります。
つまり、私たちが空を青く見ているのは、散乱した光が目のところまで到達しているためです。
なぜか4Bだけ1行で、9Bと27Bはちゃんと3行で答えてくれています。
時間を集計してみます
ちょっと気になったので、何度か動かして時間を集計してみます。
今回25回実行して次のステップの長さを集計してみます。
Download Modelcall
Download Model ステップはBonsai 2のモデルそのものをダウンロードするステップです。
| 2B | 4B | 9B | 27B | |
|---|---|---|---|---|
| 容量 | 494M | 1.1G | 2.3G | 6.8G |
| 最大 (秒) | 8.0 | 11.0 | 26.0 | 73.0 |
| 最小 (秒) | 1.0 | 2.0 | 6.0 | 18.0 |
| 平均 (秒) | 4.0 | 8.12 | 14.33 | 43.04 |
| 中央値 (秒) | 4.0 | 9.0 | 14.5 | 43.0 |
モデルサイズが大きくなると時間がかかるようですが、ローカルの端末にダウンロードするときと比べてかなり早いので27Bでも使いやすそうです。
call ステップはcurlでOpenAI互換のAPIを叩いて回答を得るステップです。
| 2B | 4B | 9B | 27B | |
|---|---|---|---|---|
| 最大 (秒) | 20.0 | 12.0 | 25.0 | 76.0 |
| 最小 (秒) | 3.0 | 1.0 | 4.0 | 17.0 |
| 平均 (秒) | 10.8 | 4.76 | 15.46 | 46.28 |
| 中央値 (秒) | 11.0 | 4.0 | 16.0 | 49.0 |
27Bでも1分程度で回答を得られるので色々使い回すことができそうです。
ただ、4Bの方が2Bよりも早いのは何でしょうね。
確かに回答の内容も2Bよりも4Bの方が短かったから、そのあたりなのでしょうか。
まとめ
以上、Bonsai 2をGithub Actionsで使ってみました。
今回の質問がシンプルだったとはいえ、27Bのモデルであっても1分程度で回答を得られたので、バッチで処理させるなど色々使い道がありそうです。
何かのお役に立てたら幸いです。










