はじめに

2026年9月22日、Raspberry Pi 公式ブログに、Cactus Compute のチームによる寄稿が載りました。書き出しはこうです。

Type “Turn the LED on” and 78 milliseconds later, the LED on our Raspberry Pi 5 comes on.

「Turn the LED on」と打つと、78 ミリ秒後に Pi 5 の LED が点く。動いているのは Needle 2 という言語モデルで、大きさは 14MB。AI HAT のような専用アクセラレータは使わず、Pi 5 の CPU だけで動いています。

Needle 2 はチャットができません。できるのは「あらかじめ渡された関数の中から、どれを呼ぶかを選び、引数を埋める」ことだけです。ここを割り切ったことで、スマホのアプリ容量どころか、マイコンのフラッシュに収まりそうな大きさまで縮みました。

電子工作の目線で言い換えると、「話しかけたら手元のハードが動く」を、クラウドにもネットにも頼らず、14MB と CPU だけで成立させる部品が手に入った、というニュースです。音声認識と組み合わせれば、ロボットや作業机の道具に「言葉のスイッチ」を付けられます。

この記事では次の 4 点を扱います。

  1. function calling(関数呼び出し)とは — LLM が文章ではなく「関数名+引数の JSON」を返す仕組みと、なぜそれが小さなモデルで足りるのか
  2. Needle 2 の中身 — 45M パラメータ・2 ビット量子化・文法で縛るデコード・確信度スコア
  3. Pi 5 で LED を点けるまで — 公式ブログの例をもとに、インストールから GPIO の LED 点灯までの手順
  4. どこに効くか — 音声で動くロボットや、ESP32 との役割分担
⚠️ 数値の帰属と時点

本記事の数値は、Raspberry Pi 公式ブログ(2026年9月22日)、Hugging Face の Needle 2 モデルカード、GitHub の README、PyPI のリリース履歴、Cactus Compute のドキュメントにあるものだけを使い、2026年9月23日に取得しました。性能の数値は Cactus Compute 側の発表値です。78ms などブログの計測は Pi 5(8GB)・Raspberry Pi OS・CPU のみ・cactus-needle 2.0.7 の条件で、冷却・電源・OS のバージョンは条件未記載です。

📌 この記事の3行まとめ
  • Needle 2 は 45M パラメータ・14MB の関数呼び出し専用モデル。 会話はせず、渡された関数の中から呼ぶものを選んで引数を埋める。関係ない依頼には空の呼び出し [] を返す。Apache-2.0
  • Pi 5 の CPU だけで、LED 点灯の関数選択が 78ms。 Python の関数に @needle.tool を付けて渡すだけで、関数名・docstring・型注釈から呼び出し仕様が組み立てられる。プロセス全体のメモリは 43〜46.4MB
  • 9月17日には後継の Needle 3 が PyPI に出ている。 いま pip install cactus-needle すると 3 系が入り、既定のモデルも Needle 3 になる。ブログの条件で試すならバージョンを 2.0.7 に固定する

🧭 1. function calling(関数呼び出し)とは — LLM に「文章」ではなく「命令」を返させる

普通の LLM は文章を返す

ChatGPT や Claude のようなチャット型の LLM は、入力された文章の続きを文章で返します。「LED をつけて」と頼めば、「LED を点灯するには GPIO を High にします……」と説明してくれるかもしれません。でも、LED は点きません。文章は文章のままで、ハードウェアには届かないからです。

function calling(関数呼び出し、tool calling とも) は、この出口を変える仕組みです。アプリ側が「呼べる関数の一覧」を LLM に渡しておくと、LLM は文章の代わりに、どの関数をどの引数で呼ぶかを JSON で返します。実際に関数を実行するのはアプリ側のプログラムです。

flowchart TB A["入力
Blink the LED 2 times"] --> B["意図の解釈
点滅させたい"] B --> C["関数の選択
blink_led"] C --> D["引数を埋める
times = 2"] D --> E["アプリが実行
GPIO を 2 回点滅"]

LLM が出すのは、たとえば次のような JSON です(Raspberry Pi 公式ブログの出力例。見やすく整形済み)。

{
  "type": "call",
  "function_calls": [
    {
      "name": "save_note",
      "arguments": { "text": "the cooler is working" }
    }
  ]
}

「Save a note that says the cooler is working.」という英文から、呼ぶべき関数 save_note と、引数 text に入れる文字列だけを抜き出しています。アプリはこの JSON を読んで、自分の save_note() を呼べばよいわけです。

関数の一覧は「説明書」として渡す

LLM に渡す関数の一覧は、関数名・説明文・引数の型を並べた JSON スキーマです。Needle のモデルカードには、手で書く場合の形が載っています。

{
  "name": "set_lights",
  "description": "Turn a room's lights on or off and set brightness",
  "parameters": {
    "type": "object",
    "properties": {
      "room": {"type": "string", "description": "which room to control"},
      "on": {"type": "boolean"},
      "brightness": {"type": "integer", "minimum": 0, "maximum": 100}
    },
    "required": ["room", "on"]
  }
}

モデルはこの説明を読んで、どの関数が依頼に合うかを判断します。当サイトの MCP ツールの作り方 で「docstring は人間へのコメントではなく、AI への仕様書」と書いたのと、まったく同じ構図です。

大きな LLM+ツール定義と、何が違うのか

function calling 自体は、GPT や Claude、Gemini などの大きな LLM でも使える機能です。違うのは位置づけです。大きな LLM にとっては「たくさんある能力のひとつ」ですが、Needle にとっては唯一の仕事です。

大きな LLM+ツール定義 Needle 2
返すもの 文章、または関数呼び出し 関数呼び出しだけ
関係ない質問 文章で答える 空の呼び出し [] を返す
動く場所 主にクラウド 端末の CPU(Pi 5・スマホなど)
モデルの大きさ 数十 GB 級も珍しくない 14MB
ネット接続 基本的に必要 初回のダウンロード後は不要

公式ブログは「フランスの首都は?」と聞いた例を載せています。Needle は { "function_calls": [] } を返しました。渡された関数のどれもその質問には答えられないからです。ブログはこれを「行動するモデルにとって、関係ない依頼を断るのが正しい応答」と書いています。

なぜ小さなモデルで足りるのか

チャット型の LLM が大きいのは、世界の知識と、どんな話題にも文章で答える力を抱えているからです。関数呼び出しに必要なのは、そのごく一部です。

  • 選ぶ先が決まっている:候補は渡された関数の一覧だけ。知らない関数を思いつく必要はない
  • 引数は入力の中にある:「2 times」から times = 2 を取り出すのに、世界の知識は要らない
  • 出力の形が決まっている:JSON の形は文法で縛れる(Needle は実際にそうしている。次の節で説明)

つまり「言葉の意味をとらえて、決まった枠に当てはめる」力に絞れば、パラメータの大部分を削れます。Needle の README は、この割り切りを「一般的な会話の能力と引き換えに、スマホ向けの関数呼び出しで 10 倍大きなモデルを上回る」と表現しています。

💡 ワード解説:文法で縛るデコード(constrained decoding)

LLM は 1 トークンずつ出力を生成します。このとき「次に出してよいトークン」を文法(JSON スキーマから作ったルール)で制限すると、出力は必ず決められた形の JSON になります。Needle 2 はスキーマからバイト単位の文法を組み立てて全トークンを縛る、とモデルカードに書いています。存在しない関数名や、型の合わない引数はそもそも出せません。


🧩 2. Needle 2 の中身 — 45M パラメータを 2 ビットに詰めた「行動専用」モデル

基本の仕様(モデルカードより)

項目 内容
開発 Cactus Compute
パラメータ数 45M(4,500 万)
ファイル 14MB の単一バイナリ(モデルとエンジンが一体)
量子化 CQ2-bit(Cactus Quants の 2 ビット)
セッションのメモリ 約 28MB(会話が長くなっても一定)
文脈 256 トークンのスライディングウィンドウ(関数定義は常に保持)
対応 CPU ARM64・x86-64・ARMv7・RISC-V・WebAssembly
対応 OS Apple・Windows・Linux・Android・Raspberry Pi
ライセンス Apache-2.0(重み・コードとも)

モデルカードの比較相手は FunctionGemma 270M、LFM2.5 230M、Apple FM といった小型モデルで、「5〜70 倍小さく、相手の f16 に対して 2 ビットで、勝ったり負けたりする」と書いています。勝ち負けの詳細はモデルカードのグラフにあり、いずれも Cactus Compute 自身の比較です。

中身の工夫

モデルカードが挙げる設計の要点は次のとおりです。

  • Simple Attention Network:Cactus Compute の小型モデル設計。FFN の代わりに Hadamard MLP、GQA アテンション、engram(n-gram をハッシュで引くキー・バリュー記憶)、複数レーンの残差接続を組み合わせる
  • バイト単位の文法でデコード:宣言した関数のスキーマから文法を作り、出力を常に正しい JSON にする
  • 確信度スコア:応答ごとに、学習済みのヘッドが出す確信度(confidence)が付く。しきい値を決めて、上なら実行・下なら聞き直す、という使い方を想定している
  • ツール検索:関数が 6 個以上あるときは、入力に近い上位 5 個だけを文脈に入れる。選ばれなかった関数は呼べない
  • メモリの上限が決まっている:256 トークンの窓をずらしながら使い、関数定義だけは固定で残す。だから会話が続いてもメモリは約 28MB から増えない

返ってくる JSON の形

モデルカードによると、1 回の応答は次のような JSON です。

{
  "type": "call",
  "function_calls": [ { "name": "set_lights", "arguments": { "room": "living room", "on": true, "brightness": 30 } } ],
  "reasoning": "'living room' -> room; 'dim' -> on true, brightness 30",
  "confidence": 0.94,
  "peak_ram_mb": 28.0
}

reasoning は、引数をどこから取ったかの短い説明です。「dim(暗く)」から brightness 30 を導いた、と読めます。モデルカードは、引数には入力に根拠のある値だけが入る、根拠のない省略可能な引数は推測せずに省く、と定めています。

動く場所

モデルカードは、Pi 5 でデコード 500 トークン/秒、Meta Quest 3S や Apple Vision Pro で 400〜1,500 トークン/秒、200 ドル以下のスマホ(Samsung A シリーズなど)で 300〜700 トークン/秒とうたっています(測定条件は条件未記載)。さらに、ピーク約 28MB のメモリで ESP32-P4 のようなマイコンにも届くと書き、第三者からは ESP32-S3 で約 11MB で動かしたという報告もある、と添えています。


🍓 3. Pi 5 で何ができるか — 公式ブログの実例

ブログで動かした関数

公式ブログは、CPU だけの Pi 5 で次の関数を宣言して試しています。

関数 中身
save_note(text) ローカルのテキストファイルにメモを追記
get_temperature() vcgencmd measure_temp で CPU 温度を読む
set_led(on) LED を点ける/消す
blink_led(times) LED を指定回数点滅させる
take_photo() rpicam-still で写真を撮る

save_noteget_temperature はコードが全文載っており、set_ledblink_ledtake_photo は関数名と使い方だけが紹介されています。ブログの言葉を借りれば、「Needle は関数を選び、その先で何が起きるかは普通の Python コードが決める」という分担です。

ブログの計測値

ブログの表をそのまま訳します。

入力 呼ばれた関数 Prefill(tok/s) Decode(tok/s) 時間(ms)
Turn the LED on. set_led 488 296 78
How hot is this Raspberry Pi? get_temperature 487 303 149
Blink the LED 2 times. blink_led 475 314 83
Take a photo. take_photo 461 248 76
Save a note that says the cooler is working. save_note 475 305 107
What is the capital of France? (なし) 470 297 92

計測条件は Pi 5(8GB)・Raspberry Pi OS・CPU のみ・cactus-needle 2.0.7 です。「時間」は complete() 1 回ぶんの壁時計時間で、選ばれた Python 関数を実行する前までを測っています。つまり 78ms は「LED をつけて」から「set_led を呼べ」という判断が出るまでの時間で、GPIO を叩く時間は含みません。冷却・電源・OS のバージョンは条件未記載です。

メモリについては、Needle 自身のセッションは約 28MB、Python の実演ではインタプリタを含むプロセス全体のピークが 43〜46.4MB だったとあります。8GB の Pi 5 から見れば誤差のような量です。

💡 ワード解説:Prefill と Decode

LLM の処理は 2 段に分かれます。Prefill は入力(関数の一覧+依頼文)をまとめて読み込む段階、Decode は出力のトークンを 1 つずつ生成する段階です。関数呼び出しは出力が短い JSON なので、全体の時間は短くなります。ブログの値は Needle のセッションカウンタから取ったものです。

実機で試す手順:インストールから LED 点灯まで

ブログの例に、GPIO の LED を点ける関数を足した形で手順にまとめます。set_ledblink_led の中身はブログに載っていないので、ここでは Raspberry Pi OS に標準で入っている GPIO Zero で書いています。

sequenceDiagram participant U as 入力 participant N as Needle 2 participant P as Python U->>N: Turn the LED on. N->>P: set_led(on=true) Note over P: led.on() で
GPIO17 の LED が点く P-->>N: {"on": true}

手順 0:配線

LED のアノード(長い足)を 330Ω の抵抗を通して GPIO17(物理ピン 11) へ、カソード(短い足)を GND(物理ピン 9) へつなぎます。Pi 5 の GPIO は 3.3V です。

手順 1:仮想環境を作ってインストール

ブログの手順は python3 -m venv needle-env ですが、GPIO Zero を仮想環境の中から使うため、--system-site-packages を付けて OS 側のパッケージも見えるようにします。バージョンはブログの計測と同じ 2.0.7 に固定します(理由は次の節)。

python3 -m venv --system-site-packages needle-env
source needle-env/bin/activate
python -m pip install "cactus-needle==2.0.7"

最初の実行時にエンジンが Hugging Face からダウンロードされ、それ以降はネットにつながずに動きます。GitHub の README は、利用状況のテレメトリが既定で有効で、環境変数 NEEDLE_TELEMETRY=0DO_NOT_TRACK=1 で止められると書いています。止めたい場合は、実行前に設定しておきます。

export NEEDLE_TELEMETRY=0
export DO_NOT_TRACK=1

手順 2:関数を宣言する

@needle.tool を付けた関数は、関数名・docstring・型注釈から呼び出し仕様が自動で組み立てられます。save_noteget_temperature はブログのコードそのまま、set_ledblink_led は GPIO Zero で書いたものです。

import subprocess
from pathlib import Path

import needle
from gpiozero import LED

led = LED(17)
notes_path = Path("needle-notes.txt")


@needle.tool
def set_led(on: bool):
    """Turn the LED on or off."""
    led.on() if on else led.off()
    return {"on": on}


@needle.tool
def blink_led(times: int):
    """Blink the LED a given number of times."""
    times = max(1, min(times, 10))  # 回数の上限は Python 側で決める
    led.blink(on_time=0.3, off_time=0.3, n=times, background=False)
    return {"blinked": times}


@needle.tool
def get_temperature():
    """Return the current CPU temperature of this Raspberry Pi in Celsius."""
    out = subprocess.check_output(["vcgencmd", "measure_temp"], text=True)
    return {"temperature_c": float(out.split("=")[1].split("'")[0])}


@needle.tool
def save_note(text: str):
    """Append a note to a local text file."""
    with notes_path.open("a", encoding="utf-8") as notes:
        notes.write(text + "\n")
    return {"text": text, "path": str(notes_path)}

手順 3:話しかける

同じファイルの末尾に、エージェントを作って入力を渡す部分を書きます。run() は、モデルが関数を選ぶ → その関数を実行する → 結果を response["results"] に入れる、までを一気に行います。

agent = needle.Needle(tools=[set_led, blink_led, get_temperature, save_note])

for prompt in ["Turn the LED on.", "Blink the LED 2 times.", "How hot is this Raspberry Pi?"]:
    agent.reset()
    response = agent.run(prompt)
    print(prompt, "->", response["results"])

reset() は会話を巻き戻し、関数の一覧は読み込んだまま残します。ブログも 2 つ目の依頼の前に agent.reset() を呼んでいます。LED が点き、2 回点滅し、CPU 温度が表示されれば成功です。「What is the capital of France?」を渡すと、どの関数も呼ばれないことも確かめられます。

✅ 自分で実行ループを回したいとき

run() を使わず、complete() で「呼ぶべき関数」だけを受け取って、実行は自分で書くこともできます(モデルカードの例)。response["type"] == "call" を確かめてから response["function_calls"][0]["arguments"] を関数に渡す形です。ブログの 78ms も、この complete() 1 回ぶんの時間です。


🚀 4. Needle 3 がもう出ている — 開発の速さと、バージョンの選び方

Pi 公式ブログの記事は 9月22日公開ですが、PyPI の履歴を見ると、Needle の開発はその先を走っています。

日付(UTC) 出来事
2026年8月10日 cactus-needle 2.0.0 を PyPI に公開
2026年8月19日 2.0.7 を公開(Pi 公式ブログの計測に使われた版)
2026年9月15日 2 系の最後の 2.0.15 を公開
2026年9月17日 3.0.0 を公開(Needle 3)
2026年9月21日 3.0.4 を公開(9月23日時点の最新)

GitHub の README と Hugging Face の Needle 3 モデルカードによると、Needle 3 はこう変わりました。

  • 大きさを選べる:2〜20 層のどの深さでも使えるように学習してあり、needle build --layers N で切り出す。20 層の needle3.cact が 29MB、4 層で 8MB
  • パラメータの大半は engram:121M パラメータのうち多くが記憶テーブルで、計算量は 50M 級に相当する、と説明している
  • Pi 5 での速さ:デコードは 20 層で約 400 トークン/秒、2 層で約 4,000 トークン/秒(ドキュメント記載。測定条件は条件未記載
  • 埋め込みベクトルも出せる:README は関数呼び出し・構造化抽出と並ぶ 3 つ目の仕事として、文のベクトルを返す機能を挙げている

Python のドキュメントによると、needle.Needle() の既定は generation=3 で、バージョンを固定せずに pip install cactus-needle すると Needle 3 が動きますgeneration=2 を指定すれば、最新のパッケージのまま Needle 2 を使い続けられます。ブログの 78ms と同じ条件で試したいなら 2.0.7 に固定し、最新の小ささと速さを試したいなら 3 系を入れる、という選び方になります。


🤖 5. どこに効くか — 「言葉のスイッチ」を手元のハードに

一次資料が示している使い道

  • GPIO Zero の関数をそのまま渡せる:ブログは「GPIO Zero の関数を書いているなら、デコレータを付けて渡すだけ」と書いている。既存のアプリのロジックも同様
  • 自分の関数に合わせて学習し直せる:ブログは「ノート PC 上でローカルに fine-tune できる」と書き、README は LoRA による手元での学習と、Cactus Compute のサーバでの学習の 2 通りを載せている
  • オフライン前提の配布:初回ダウンロード後はネット不要。ドキュメントには、ネットのない端末へファイルを運んで入れる手順もある

ESP32 との分担を考える

モデルカードは ESP32-P4 に「届く」と書いていますが、手元で試すなら、まずは Pi 5 が言葉を関数に変え、ESP32 がハードを動かすという分け方がいちばん素直です。

flowchart TB A["音声認識
またはキーボード"] --> B["Pi 5
Needle が関数を選ぶ"] B --> C["Python の関数
シリアル/HTTP で送る"] C --> D["ESP32
サーボ・赤外線・LED"]

Needle の出力は JSON なので、そのまま ESP32 に投げても、Python 側で短いコマンドに変換してから送っても構いません。

📌 筆者の見方

ここからは筆者の意見です。

いちばん面白いと感じたのは、「断る」ことが仕様として決まっている点です。当サイトのスタックチャンは Gemini の Live API で話していて、会話はとても自然ですが、どんな依頼にも何かしら言葉を返そうとします。ロボットの首を回す、エアコンのリモコンを送る、といった「体を動かす」部分では、それはむしろ困る性質です。関数に当てはまらなければ [] を返し、引数は入力に根拠のある値しか入れない。この割り切りは、ハードを動かす側から見るとありがたい設計です。

もうひとつは、MCP ツールの作り方で書いた「docstring が AI への仕様書になる」という書き味が、14MB のモデルでもそのまま通用することです。クラウドの大きな LLM 向けに磨いた関数の書き方が、机の上の Pi 5 にもそのまま持ち込める。関数の設計さえ丁寧にしておけば、呼び出す側のモデルが大きくても小さくても、同じ関数を使い回せる時代になってきた、と受け止めています。

会話はクラウドの大きな LLM、手元のハードを動かす判断は端末の小さな LLM、という二段構えは、個人の電子工作でも十分に組める大きさになりました。


⚠️ 注意点

  • 入力は英語が前提:ブログは「plain English」を行動に変えると書いており、一次資料の例はすべて英語です。日本語入力への対応は一次資料に記載がありません
  • 関数の説明が精度を決める:モデルは関数名・docstring・型注釈を読んで判断します。名前は依頼に出てきそうな言葉にし、引数の意味は docstring に書くのが基本です。Needle 2 のモデルカードは、取りうる値を Literal で列挙すると、モデルはその中からしか選べなくなると説明しています
  • 誤作動の安全弁は Python 側に置く:ブログの言葉どおり、関数を選ぶのは Needle で、その先で何をするかは Python のコードです。点滅回数の上限や、危険な操作の前の確認は関数の中で決めます。応答の confidence がしきい値を下回ったら実行せずに聞き直す、という使い方もモデルカードが勧めています
  • バージョンで既定のモデルが変わる:3 系では Needle() の既定が Needle 3 です。記事やブログの数値と比べるときは、どちらの世代で動いているかを先に確かめます

まとめ

  • Needle 2 は、会話をしない関数呼び出し専用の LLM。 45M パラメータ・2 ビット量子化・14MB の単一バイナリで、渡された関数の中から呼ぶものを選び、引数を埋める。関係ない依頼には [] を返す。Apache-2.0
  • function calling は、LLM の出口を文章から命令に変える仕組み。 候補は渡された関数だけ・引数は入力の中・出力の形は文法で縛る、の 3 つが揃うから小さなモデルで足りる
  • Pi 5 の CPU だけで、LED 点灯の関数選択が 78ms。 条件は Pi 5(8GB)・CPU のみ・cactus-needle 2.0.7。冷却と電源は条件未記載。プロセス全体のメモリは 43〜46.4MB
  • @needle.tool を付けた Python 関数を渡すだけで動く。 GPIO Zero の関数もそのまま使える。ブログの条件で試すなら 2.0.7 に固定する
  • 後継の Needle 3 は 9月17日から PyPI にある。 8〜29MB で深さを選べる。既定のモデルが 3 に変わったので、世代を意識してインストールする

よくある質問(FAQ)

Q. Needle 2 はチャットボットとして使えますか?

A. 使えません。公式ブログは「Needle はチャットボットではない」と明言しています。渡された関数の中から呼ぶものを選んで引数を埋めることだけを学習したモデルで、どの関数にも当てはまらない質問には空の呼び出し [] を返します。

Q. Raspberry Pi 5 に AI HAT は必要ですか?

A. 必要ありません。公式ブログの計測は、専用の AI HAT を使わず Pi 5 の CPU だけで行われています。条件は Pi 5(8GB)・Raspberry Pi OS・cactus-needle 2.0.7 で、「Turn the LED on」から set_led を選ぶまでが 78ms でした。

Q. インターネットにつながっていなくても動きますか?

A. 動きます。最初の実行でエンジンをダウンロードしたあとは、クラウドの API もネット接続も使わずに動く、と公式ブログに書かれています。ネットのない端末へファイルを運んで入れる手順も、Cactus Compute のドキュメントにあります。

Q. 日本語で指示できますか?

A. 一次資料には日本語対応の記載がありません。公式ブログは「plain English」を行動に変えると書いており、モデルカードや README の例もすべて英語です。

Q. Needle 2 と Needle 3 のどちらを使えばよいですか?

A. 公式ブログと同じ条件で試すなら、pip install "cactus-needle==2.0.7" で Needle 2 に固定します。9月17日以降の 3 系をそのまま入れると既定で Needle 3 が動き、8〜29MB の範囲で大きさを選べます。3 系のまま Needle 2 を使いたい場合は generation=2 を指定します。

関連記事

参考

一次資料は front matter の references に列挙しています。取得日はすべて 2026年9月23日です。

X(旧 Twitter)の投稿は出典に使っていません。