PrismMLのBonsai 2をGithub Actionsで動かしてみる

PrismMLのBonsai 2をGithub Actionsで動かしてみる

PrismMLが公開した超軽量LLM「Bonsai 2」をGithub Actions上で動かしてみました。2B〜27Bの4つのモデルサイズすべてを試し、ワークフロー定義からセットアップ方法、実行結果や処理時間の集計結果をまとめます。
2026.10.19

こんばんは、情報システム室の夏目です。

先日PrismMLが新しい超低ビットオープンウェイトモデルのBonsai 2を公開しました。

https://prismml.com/news/bonsai-2-family

軽量モデルではスマホ端末での使用も可能で、iPhone 18 ProにおいてBonsai 2 9Bでは最大 34 tokens/sec 、Bonsai 2 2Bでは最大 120 tokens/sec の処理速度が出るそうです。

スマホでも動くモデルということで、今回はこのモデルをGithub Actions上で動かそうと思います。

Bonsai 2

https://huggingface.co/collections/prism-ml/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トリガーを使って試す部分を使い回す形でワークフローを作成します。

exec.yml
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を定義したので、これを呼び出すワークフローを書いていきます。

call_2b.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"
call_4b.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-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"
call_9b.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-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"
call_27b.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-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分弱で回答しています。
色々作業をさせるのにも使えそうです。

ちなみに回答は次のようになっていました。

2B
空の色は、光が空を通って色として現れる現象に起因します。この現象は主に以下の通り説明できます。

1.  **光波の色**: 空中の光は、すべてを赤を最も長い波長に広がり、紫外線を長くし、遠くから視認できない青の光を生成します。この「赤の光」の長さが空の色を決定的に捉えることができます。
2.  **観測の距離**: 遠くから空を見ていると、遠端の赤い光をより鮮明に見えるため、空が赤いように見える現象です。
3.  **気体の温度影響**: 暖かい空(例えば夏や陽午)では、水蒸気などが増加するが、この影響は赤い色ではなく、青く見えるものではありません。

結論として、空が青いのは**「赤い光の長さが赤い色より短く」**という物理的な関係にあります。
4B
空が青いのは、地球からの陽光が大気中での水素分子などに散乱して、私たちがその光を見ることがからなるからです。
9B
空の色は、大気の成分である窒素と酸素の粒子が太陽の光を散らかすことで生まれます。
赤や青など色分けされる現象は、光の波長によって粒子との衝突の仕組みが異なるためです。
波長の短い青い光よりも赤い光の方が長いため、青い色が見えます。
27B
太陽光が大気中のガスや塵に衝突して、他の色と比べて波長の短い光が最も強く散乱されるためです。
この現象を「レイリー散乱」と呼び、その結果、空全体が青く見えることになります。
つまり、私たちが空を青く見ているのは、散乱した光が目のところまで到達しているためです。

なぜか4Bだけ1行で、9Bと27Bはちゃんと3行で答えてくれています。

時間を集計してみます

ちょっと気になったので、何度か動かして時間を集計してみます。

今回25回実行して次のステップの長さを集計してみます。

  • Download Model
  • call

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分程度で回答を得られたので、バッチで処理させるなど色々使い道がありそうです。

何かのお役に立てたら幸いです。


AI白書2026 配布中

クラスメソッドが独自に行なったAI診断調査をもとに、企業のAI活用の現在地を調査レポートとしてまとめました。企業規模別の活用度傾向に加え、規模を超えてAI活用を進める企業に共通する取り組みまで、自社の現在地を捉えるためのヒントにぜひ。

AI白書2026

無料でダウンロードする

この記事をシェアする

DevelopersIO 2026

関連記事