1-bit LLM「Bonsai-8B」をMacでローカル実行する — セットアップからチャットUI構築まで


はじめに

「8Bパラメータのモデルが1.28GBに収まる」— 最初に聞いた時は信じられなかった。

PrismML が2026年3月末にリリースした Bonsai-8B は、重みを1ビットに圧縮した LLM。フルプレシジョン(FP16)なら16.38GBになるところを、92%圧縮して1.28GB。それでいてベンチマーク平均70.5と、フルプレシジョンの8Bモデルに匹敵する性能を出す。

これを MacBook Air M3(8GB)でローカル実行し、Svelte + FastAPI でチャットUIを構築するまでの全過程を記録する。


Bonsai-8B とは

項目仕様
パラメータ8.19B(~6.95B non-embedding)
アーキテクチャQwen3-8B dense(GQA, SwiGLU MLP, RoPE)
量子化1-bit(0→−scale, 1→+scale)、128重みごとにFP16スケール係数
実効ビット幅1.125 bits/weight
ディスクサイズ1.28 GB
コンテキスト長65,536 tokens
ライセンスApache 2.0

各重みが「0か1か」の1ビット。128個の重みごとにFP16のスケール係数を1つ共有する。この構造のおかげでメモリ14x削減、推論6x高速、エネルギー5x削減を実現している。

性能レベルとしては GPT-3.5-turbo 相当。GPT-4oやClaude Sonnetには遠く及ばないが、分類・要約・簡単な質疑応答なら十分実用的。価値は賢さではなく「1.28GBでローカルで57tok/sで動く」こと。


セットアップ

前提環境

  • MacBook Air 2024(M3, 8GB RAM)
  • macOS(Darwin 23.6.0)
  • Python 3.13
  • Xcode.app が必要(Command Line Tools だけでは不可)

なぜ Xcode.app が必要か

Bonsai-8B の MLX 版は、PrismML が fork した カスタム MLX を使う。1-bit カーネルが含まれているため、標準の pip install mlx では動かない。

この fork をソースビルドするには Metal コンパイラが必要で、Xcode.app に含まれる metal ユーティリティがないとビルドが失敗する。Command Line Tools だけでは xcrun: error: unable to find utility "metal" になる。

# Xcode.app インストール後
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
sudo xcodebuild -license accept

# 確認
xcrun --find metal
# → /Applications/Xcode.app/Contents/Developer/Toolchains/.../metal

PrismML fork MLX のインストール

pip install "mlx @ git+https://github.com/PrismML-Eng/mlx.git@prism"

初回ビルドは数分かかる。成功すると mlx-0.31.2.dev... がインストールされる。

モデルのダウンロードと動作確認

from mlx_lm import load, generate

model, tokenizer = load("prism-ml/Bonsai-8B-mlx-1bit")

response = generate(
    model,
    tokenizer,
    prompt="Explain quantum computing in simple terms.",
    max_tokens=256,
)
print(response)

初回はモデルダウンロード(~1.3GB)が走る。2回目以降は ~/.cache/huggingface/ からキャッシュ読み込み。


ベンチマーク

MacBook Air M3(8GB)での実測値:

テストmax_tokens生成tokens時間tok/s
短文EN32310.79s39.4
中文EN1281282.28s56.2
長文EN5125128.79s58.2
短文JP32320.70s45.4
中文JP1281282.29s55.9
長文JP5125128.92s57.4
  • 安定速度: 55-58 tok/s(長文で安定、短文は初期化コストで低め)
  • メモリ: Active 1.19GB / Peak 1.23GB
  • 参考: 公式の M4 Pro では 131 tok/s

日本語でも英語とほぼ同等の速度が出る。

品質テスト

テスト結果
推論(三段論法)思考は正しいが結論を明示せず続く
コード生成関数定義前に説明が始まり不完全
事実(JFK)ハルシネーション(Polk と誤答)
数学(17×23)計算過程を説明するが答えに到達しない
日本語推論正答するが同じ文を7回繰り返す

1-bit の限界が見える。事実の記憶が弱く、繰り返しが起きやすい。用途を選べば十分実用的だが、知識応答や複雑な推論には向かない。


チャットUI の構築

アーキテクチャ

Browser (Svelte)  ←WebSocket→  FastAPI (Python)  ← MLX → Bonsai-8B
  • バックエンド: FastAPI + WebSocket ストリーミング
  • フロントエンド: SvelteKit(static build → FastAPI が配信)
  • 推論: mlx-lm の stream_generate()run_in_executor で非同期実行

バックエンド(server.py)

from fastapi import FastAPI, WebSocket
from mlx_lm import load
from mlx_lm.generate import stream_generate
from mlx_lm.sample_utils import make_sampler, make_logits_processors

model, tokenizer = load("prism-ml/Bonsai-8B-mlx-1bit")

@app.websocket("/ws/chat")
async def websocket_chat(websocket: WebSocket):
    await websocket.accept()
    data = await websocket.receive_text()
    msg = json.loads(data)

    sampler = make_sampler(temp=0.5, top_k=20, top_p=0.9)
    logits_processors = make_logits_processors(
        repetition_penalty=1.2,
        repetition_context_size=64,
    )

    # stream_generate は同期なので executor で実行
    responses = await loop.run_in_executor(None, lambda: list(
        stream_generate(model, tokenizer, prompt=prompt,
                       sampler=sampler, logits_processors=logits_processors)
    ))

    for resp in responses:
        await ws.send_text(json.dumps({"type": "token", "text": resp.text}))

ポイント:

  • StaticFiles(directory="frontend/build", html=True) で SvelteKit のビルドをルート配信
  • API/WebSocket ルートを先に定義し、static mount は最後(catch-all)

繰り返し問題の対策

1-bit モデルで最も目立つ問題が繰り返し(repetition)。mlx-lm の make_logits_processors で対策できる:

logits_processors = make_logits_processors(
    repetition_penalty=1.2,      # 繰り返しトークンのスコアを下げる
    repetition_context_size=64,  # 直近64トークンを監視
)

repetition_penalty の目安:

  • 1.0: ペナルティなし(繰り返しが起きやすい)
  • 1.2: 軽いペナルティ(推奨デフォルト)
  • 1.5: 強めのペナルティ(多様性は上がるが coherence が落ちる場合も)

フロントエンド(Svelte)

SvelteKit で簡素なチャットUIを構築。デザインは「盆栽(Bonsai)」の名前から Wabi-Sabi × Terminal の美学 — 墨色の背景に侘び寂びの余白、モノスペースフォントが端末のように流れる。

主な機能:

  • WebSocket ストリーミング(トークンが1つずつ流れる)
  • サンプリングパラメータのリアルタイム調整(temp, top_k, top_p, rep_penalty)
  • tok/s リアルタイム表示
  • 自動再接続(3秒間隔、ループ防止付き)

Svelte 5 での WebSocket 接続の注意点

Svelte 5 の $effect() 内で WebSocket 接続すると、リアクティブな依存追跡で再接続が無限ループする。onMount() を使うこと:

<!-- ❌ $effect は依存追跡で無限ループ -->
$effect(() => { connect(); });

<!-- ✅ onMount は1回だけ -->
onMount(() => {
  connect();
  return () => { ws?.close(); };
});

チューニングの可能性 — Bankai(XOR パッチ)

1-bit モデルでは従来の LoRA / Fine-tuning は使えない。重みが0/1しかないので勾配ベースの学習が効かない。

代わりに Bankai という新しい手法がある:

従来: LoRA → FP16 adapter (~100MB) → swap が遅い
Bankai: XOR patch → JSON (~KB) → ゼロオーバーヘッドで適用/除去

1-bit の重みを行単位でビット反転(XOR)する。パッチは KB 単位の JSON ファイルで、同じ XOR 操作で適用も除去もできる(完全可逆)。Scale 値に基づく Greedy Hill-Climbing で効率的に探索し、ランダムの 3.88x の行動変化を実現する。

これは面白い構造で、対象(モデル)を変えずに意味(行動)だけを変える、可逆で合成可能な操作 — まさにレンズのような性質を持っている。KB単位のパッチでペルソナを切り替えたり、タスク特化の行動変容を実現できる可能性がある。


まとめ

項目結果
モデルサイズ1.28 GB(FP16比 92%圧縮)
実行速度55-58 tok/s(M3 8GB)
メモリ使用1.19 GB
性能レベルGPT-3.5-turbo 相当
向いてる用途分類・要約・抽出・短文応答
向いてない用途事実応答・複雑推論・長文コード
チューニングBankai(XOR patch)で行動変容可能

1.28GB で 8B パラメータ。MacBook Air の 8GB メモリでも余裕を持って動く。性能は GPT-3.5 級で万能ではないが、「ローカルで常時起動できる軽量LLM」としての価値は大きい。

Bankai の XOR パッチによるチューニングは、従来の LoRA とは全く異なるアプローチで、1-bit モデルならではの可能性を開いている。今後、ペルソナ切替やタスク特化での活用を実験していきたい。


MacBook Air M3 8GB で検証。2026-04-05。