所要時間: 約60分 | 難易度: ★★★★☆
この記事で作るもの
- Jetson Orin NX 16GB上で、Googleの軽量LLM「Gemma」を完全オフライン動作させる制御システム。
- センサー入力を模したデータに対し、LLMがリアルタイム(TTFT約200ms)で判断を下し、行動プロンプトを生成するPythonスクリプト。
- 前提知識:Pythonの基本的な読み書きができ、Linuxコマンド(Ubuntu)の操作に抵抗がないこと。
- 必要なもの:Jetson Orin NX 16GB開発者キット、NVMe SSD(128GB以上推奨)、DC電源。
📦 この記事に関連する商品(楽天メインで価格確認)
Jetson Orin NX 16GB16GBのVRAMがLLM駆動に必須。エッジAI開発の標準機。
※アフィリエイトリンクを含みます
先に確認するスペック・料金
Jetson Orin NXには8GB版と16GB版がありますが、LLMを動かすなら16GB一択です。 Gemma-2-9Bクラスのモデルを4bit量子化で動かす場合、VRAM消費は約5〜6GBですが、OSや周辺制御、将来的なRAG(検索拡張生成)の実装を考えると、8GB版では確実にメモリ不足に陥ります。
Orin NX 16GBモジュール単体で約10万円、キャリアボード込みで15万円程度の投資が必要です。 もし予算が厳しい場合は、RTX 3060(12GB)を搭載したPCで代用可能ですが、今回のテーマである「スーツケースに収まるサイズでオフライン動作」というモビリティは失われます。 MacBook Air(M2/M3 16GB以上)も良い選択肢ですが、GPIO等の物理センサー連携に別途変換が必要になるため、ロボット製作ならJetsonに軍配が上がります。
なぜこの方法を選ぶのか
クラウドAPI(GPT-4等)を使えば、もっと賢いロボットは数分で作れます。 しかし、移動体ロボットにおいて「電波がないと動かない」「通信ラグが1秒ある」ことは致命的です。 Redditの事例でも強調されている通り、完全オフラインにすることで、プライバシーの確保とゼロレイテンシに近い反応速度を両立できます。
今回は推論エンジンに「llama-cpp-python」を採用します。 TensorRT-LLMの方がJetsonの性能を極限まで引き出せますが、ビルドの難易度が極めて高く、モデル変更のたびに数時間のコンパイルが必要です。 llama-cpp-pythonなら、GGUF形式のモデルファイルを置くだけですぐに動かせ、実用上十分なレスポンス(200ms以下の初動)が得られるため、開発スピードを優先しました。
Step 1: 環境を整える
Jetson特有のセットアップから始めます。まずはJetPackのバージョンを確認し、必要なビルドツールを入れます。
# OSの状態を確認
cat /etc/nv_tegra_release
# システムのアップデートと基本ツールのインストール
sudo apt-get update
sudo apt-get install -y python3-pip cmake git build-essential
# Jetsonのパフォーマンスを最大化(MAXNモードへ変更)
sudo nvpmodel -m 0
sudo jetson_clocks
nvpmodel -m 0は、Orin NXの電力制限を解除し、全コアをフル稼働させる設定です。
これを忘れると、LLMの推論速度が半分以下に落ち込むことがあります。
⚠️ 落とし穴: Jetsonはデフォルトでスワップ領域が不足しています。LLMのロード時にメモリ不足でプロセスが強制終了(OOM Killer)されるのを防ぐため、必ずZRAMまたはSSD上にスワップを作成してください。
# 8GBのスワップファイルを作成する例
sudo fallocate -l 8G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
Step 2: 基本の設定
llama-cpp-pythonを、CUDA(JetsonのGPUコア)を有効にした状態でビルドします。 ここが一番のハマりポイントですが、以下の環境変数を指定してインストールすることで、CPUではなくGPUでの高速推論が可能になります。
# CUDAのパスを通す(環境によって異なる場合があるため要確認)
export CUDA_HOME=/usr/local/cuda
export PATH=$PATH:$CUDA_HOME/bin
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$CUDA_HOME/lib64
# CUDAを有効にしてインストール
# Jetson Orinのアーキテクチャ(sm_87)に最適化
CMAKE_ARGS="-DGGML_CUDA=ON" pip3 install llama-cpp-python --no-cache-dir
次に、モデルをダウンロードします。今回はHugging FaceからGemma-2-9BのGGUF版を取得します。 私の検証では、Orin NX 16GBなら「Q4_K_M」という量子化サイズが、速度と精度のバランスが最も良かったです。
# モデル用のディレクトリ作成
mkdir ~/models && cd ~/models
# 軽量なGemma-2-9B-ITの量子化モデルをダウンロード
# (wget等がなければインストールしてください)
wget https://huggingface.co/bartowski/gemma-2-9b-it-GGUF/resolve/main/gemma-2-9b-it-Q4_K_M.gguf
Step 3: 動かしてみる
まずは、オフラインでLLMが動くか最小限のコードでテストします。
JetsonのGPU(CUDA)を認識させるために n_gpu_layers=-1 を指定するのがコツです。
import os
from llama_cpp import Llama
# モデルのパスを指定
model_path = os.path.expanduser("~/models/gemma-2-9b-it-Q4_K_M.gguf")
# LLMの初期化
# n_gpu_layers=-1 は全てのレイヤーをGPUにロードする設定
# n_ctx=2048 は文脈の長さ(ロボットの短期記憶)
llm = Llama(
model_path=model_path,
n_gpu_layers=-1,
n_ctx=2048,
verbose=False
)
# ロボットへの指示
prompt = "USER: 前方に障害物があります。回避行動を短く指示してください。\nASSISTANT:"
# 推論実行
output = llm(
prompt,
max_tokens=50,
stop=["USER:", "\n"],
echo=False
)
print(output["choices"][0]["text"])
期待される出力
左に30度旋回し、速度を落として直進を継続してください。
このレスポンスが1秒以内に返ってくれば、GPUでの推論が正常に動作しています。
もし出力に数十秒かかる場合は、n_gpu_layersが効いておらずCPUで動いている可能性が高いです。
Step 4: 実用レベルにする
ロボットとして運用する場合、LLMの推論中もセンサーデータの監視を止めるわけにはいきません。 また、プロンプトに「現在のセンサー値」を動的に埋め込む必要があります。 実務で使えるレベルの、非同期処理を取り入れた構造に拡張しましょう。
import time
import threading
from llama_cpp import Llama
class SuitcaseRobot:
def __init__(self, model_path):
self.llm = Llama(model_path=model_path, n_gpu_layers=-1, n_ctx=1024)
self.current_sensors = {"ultrasonic": 0.0, "battery": 100}
self.running = True
def update_sensors(self):
"""センサー値を擬似的に更新し続けるスレッド(実機ではここでGPIOを読む)"""
while self.running:
# 擬似データ:障害物との距離が徐々に短くなる
self.current_sensors["ultrasonic"] = 50.5
time.sleep(0.1)
def think(self, user_input):
"""センサー情報を加味してLLMに判断を仰ぐ"""
system_prompt = f"System: 現在の距離センサーは {self.current_sensors['ultrasonic']}cm です。"
full_prompt = f"{system_prompt}\nUSER: {user_input}\nASSISTANT:"
start_time = time.time()
response = self.llm(full_prompt, max_tokens=64, stop=["USER:"])
end_time = time.time()
print(f"思考時間: {end_time - start_time:.2f}秒")
return response["choices"][0]["text"]
# 実行
model_file = os.path.expanduser("~/models/gemma-2-9b-it-Q4_K_M.gguf")
robot = SuitcaseRobot(model_file)
# センサー監視スレッド開始
sensor_thread = threading.Thread(target=robot.update_sensors)
sensor_thread.start()
# 思考実行
decision = robot.think("状況を判断して、次のアクションを教えて。")
print(f"ロボットの判断: {decision}")
# 終了処理
robot.running = False
sensor_thread.join()
この構成のポイントは、system_promptにリアルタイムの数値を流し込んでいる点です。
Redditのスレッドにある「30個のセンサー」も、このように辞書形式で管理し、推論の直前に文字列としてプロンプトへ結合します。
これだけで、LLMは「今、自分の周りで何が起きているか」を理解した上で言葉を発するようになります。
よくあるトラブルと解決法
| エラー内容 | 原因 | 解決策 |
|---|---|---|
CUDA error: out of memory | VRAM不足。OSやGUIがメモリを消費している。 | sudo init 3でGUIを停止し、コンソールモードで実行する。 |
Illegal instruction (core dumped) | llama-cppのビルド設定がアーキテクチャと不一致。 | CMAKE_ARGSを見直し、no-cache-dirを付けて再インストール。 |
| 推論が極端に遅い(1文字/秒以下) | GPUではなくCPUで動いている。 | n_gpu_layers=-1が設定されているか、CUDAビルドが成功しているか確認。 |
次のステップ
ここまでで、「脳(LLM)」と「感覚(センサー)」を繋ぐ基礎ができました。 次のステップとしては、ロボットに「声」を与えるための「Whisper(音声認識)」や「Piper(音声合成)」の導入をおすすめします。 これらもJetson上でオフライン動作が可能です。
さらに実用性を高めるなら、RAG(Retrieval-Augmented Generation)の構築に挑戦してください。 ロボットの操作マニュアルや過去の走行ログをベクトルデータベース(ChromaDBなど)に保存し、状況に応じてLLMがそれを参照するようにすれば、より「賢い」振る舞いが可能になります。 RTX 4090を積んだデスクトップとは違い、Jetsonには16GBという制約があります。 その限られたリソースの中で、いかに軽量なモデルと効率的なプロンプトを組み合わせるか。 それがエッジAIエンジニアとしての腕の見せ所です。
よくある質問
Q1: Orin Nanoでも同じことができますか?
Orin Nano 8GBでも動作は可能ですが、非常に厳しいです。モデルをQ2(2bit)まで量子化すればロードできますが、知能が著しく低下し、会話が成立しなくなることが多いです。実用的なロボットを目指すならOrin NX 16GBを強く推奨します。
Q2: WiFiなしでどうやってライブラリを入れるのですか?
セットアップ時のみ有線LANやテザリングを使用します。一度環境が構築できれば、llama-cpp-pythonはインターネット接続なしで動作します。Redditの事例のように「完全オフライン」にするのは、あくまで運用フェーズの話です。
Q3: バッテリーはどのくらい持ちますか?
Orin NXの消費電力は最大25W程度です。10,000mAhのモバイルバッテリー(PD出力対応)を使えば、アイドル時を含めて2〜4時間程度の稼働が目安です。LLMの推論を回し続けると消費電力が跳ね上がるので、大容量のバッテリー選定が重要になります。






