Raspberry Pi 5にUbuntuをヘッドレスでセットアップしてローカルLLMを動かす
Raspberry Pi 5にUbuntuをヘッドレスでセットアップしてローカルLLMを動かす
概要
- Raspberry Pi 5 / 8Gをヘッドレスでセットアップ(つなぐのは電源ケーブルだけ)
- Ubuntu Server (Raspberry Pi OSは使わない)
- 最低限のセキュリティ設定(sshd, ファイアウォール)
- ローカルLLMのセットアップ(llama.cpp, gemma-2b-it)
- サーバ設定(自動マウント, スワップファイル, Samba, Apache)
- Python開発環境(pyenv, Poetry)
ハードウェア
- 本体: Raspberry Pi 5 / 8GB
- 電源: Anker PowerPort III Nano 20W (5V 3A対応)
- USBケーブル: Anker PowerLine III USB-C & USB-C 2.0 ケーブル
- microSDカード: SanDisk Ultra 128GB class 10
- ファン: Raspberry Pi 5用アクティブクーラー
- ケース: Raspberry Pi 5用公式ケース
Note: 公式ケースの価格がそこそこ高く、かつ、アクティブクーラーがあればケース付属のファンが無駄になるので、サードパーティ製のケースが出てくるのを待ったほうが良いかもしれない。
OSイメージを焼く
Raspberry Pi ImagerでOSイメージを焼く。
以下設定でOSイメージを焼く。サーバとして利用するだけなのでUbuntu Server
を選択。パスワードは平文でどこかにキャッシュされるといやなので、適当なパスワードを一時的に設定しておいて、sshでログイン後に真っ先に変更する。
- Raspberry Piデバイス
Raspberry Pi 5
- OS
Ubuntu Server 23.10 (64-bit)
- ストレージ
- microSDカード
- ホスト名
-
pi5
.local
-
- ユーザ名とパスワード
-
ota
,適当なパスワード
(→sshログイン後すぐ変える)
-
- Wi-Fi
-
SSID
,パスワード
,JP
-
- ロケール
-
Asia/Tokyo
,jp
-
- SSH有効化
公開鍵認証のみを許可する
- 公開鍵 (e.g.
~/.ssh/id_rsa.pub
) の中身をペースト
- テレメトリーを有効化
- オフ
sshでログイン
- microSDカードをRaspberry Pi 5に挿入
- 電源を入れる
- 少し待ってPCからsshでログイン
ssh ota@pi5.local
- 公開鍵認証でログイン出来るはず
- ホスト名が解決できなければIPアドレスを指定
- うまくいかなければ
-vvv
をつけて原因を調べる - どうしてもだめならOSイメージから焼き直し
-
~/.ssh/know_hosts
に残っている古いエントリを削除しておく
-
パスワード変更
ログイン後、すぐにパスワードを変更する。
passwd
システムのアップグレード
結構時間がかかるはず。
sudo apt-get update
sudo apt-get full-upgrade -y
sudo reboot
full-upgrade
すると再起動以後ファンコントロール(温度によって強弱)が有効になる。しないとファンは回りっぱなし。
sshの設定
~/.ssh/authorized_keys
の確認。~/.ssh/id_rsa.pub
の中身が含まれているはず。パーミッションも 600
になっているはず。
cat ~/.ssh/authorized_keys
ls -l ~/.ssh/authorized_keys
sshdの設定を変更。以下4つを変更する。
- rootユーザでのログインを禁止
- パスワード認証を禁止
- PAMを無効化(これはしなくてもOK)
- X11Forwardingを無効化
sudo cp -p /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
sudo nano /etc/ssh/sshd_config
以下4行を変更。
PermitRootLogin no
PasswordAuthentication no
UsePAM no
X11Forwarding no
変更を確認。
diff -u /etc/ssh/sshd_config.bak /etc/ssh/sshd_config
sudo sshd -T | grep root
sudo sshd -T | grep pass
sudo sshd -T | grep pam
sudo sshd -T | grep x11
マシンを再起動。
sudo reboot
再度sshでログイン。
ssh ota@pi5.local
ファイアウォールの設定
外部マシンからアクセスするポートだけ許可する。22
だけは開けておかないとsshでログイン出来なくなり、OSイメージを焼くところからやり直しになるので注意。
sudo ufw status verbose
sudo ufw enable
sudo ufw default DENY
sudo ufw allow 22 # ssh
# sudo ufw allow 80 # http for apache
# sudo ufw allow 8080 # http for llama.cpp
# sudo ufw allow 139 # samba
# sudo ufw allow 445 # samba
sudo ufw status verbose
sudo reboot
bashの設定
cat >> ~/.bash_aliases <<EOF
alias ll='ls -l'
alias la='ls -A'
alias l='ls -CF'
alias rm='rm -i'
alias watch-temp='watch -n 1 -d -t vcgencmd measure_temp'
EOF
source ~/.bash_aliases
LANG
を設定しておかないと、日本語ファイル名が文字化けすることがある。
cat >> ~/.bashrc <<EOF
export LANG=C.UTF-8
EOF
tmuxの設定
cat >> ~/.tmux.conf <<EOF
set -g prefix C-j
unbind C-b
bind C-j send-prefix
set -g base-index 1
set -g status-right "%H:%M"
set -g window-status-current-style "underscore"
set -g default-terminal "screen-256color"
EOF
tmux
を実行して Ctrl-j d
でデタッチして tmux a
でアタッチ出来ることを確認。
htopの設定
htop
でCPU温度を表示する。
libsensors5
をインストール。
sudo apt-get install -y libsensors5
-
htop
を実行 -
F2 (Setup)
で設定画面 -
Display options
-Also show CPU temperature (requires libsensors)
を有効 -
F10
で保存
ローカルLLM(gemma-2b-it)のセットアップ
gemma
以外のローカルLLMについては別記事で書く予定。
Note:
llama-cpp-python
よりllama.cpp
を素で使った方が推論時間が速いのでllama.cpp
をサーバモードで立ち上げることにする。
llama.cppのインストール
sudo apt-get update
sudo apt-get install -y aria2 build-essential cmake
git clone https://github.com/ggerganov/llama.cpp
cd llama.cpp
make -j
モデルのダウンロード
gemma を使う際は Terms of Use に同意する必要がある。
gemma-2b-it
の6bit量子化版をダウンロード(1.9GB)。
aria2c -x 5 "https://huggingface.co/mlabonne/gemma-2b-it-GGUF/resolve/main/gemma-2b-it.Q6_K.gguf" -d "models" -o "gemma-2b-it.Q6_K.gguf"
7b 版を試す場合は、gemma-7b-it
の2bit量子化版をダウンロード(3.2GB)。これより大きいモデルはメモリが足りず安定動作しなかった。
aria2c -x 5 "https://huggingface.co/mlabonne/gemma-7b-it-GGUF/resolve/main/gemma-7b-it.Q2_K.gguf" -d "models" -o "gemma-7b-it.Q2_K.gguf"
実行してみる。
./main -ngl 0 -t 4 -c 8192 -m "models/gemma-2b-it.Q6_K.gguf" -p "Where is the capital city of Japan?"
-
gemma-2b-it.Q6_K.gguf
- 毎秒5トークン程度で生成できる。
-
gemma-7b-it.Q2_K.gguf
- 毎秒2.5トークン程度で生成できる。
- Raspberry Pi 5 / 8GBでまともに動くのは7bの2bit量子化版まで。3bit量子化版も試したが、メモリが足らず安定動作しなかった。
対話モードで実行。
./main -ngl 0 -t 4 -c 8192 -m "models/gemma-2b-it.Q6_K.gguf" -e -i --color \
-p "<start_of_turn>user\nHello<end_of_turn>\n<start_of_turn>model\n" \
--in-prefix "<start_of_turn>user\n" \
--in-suffix "<end_of_turn>\n<start_of_turn>model\n"
LLMサーバ起動。
8080
ポートを公開するので、先にファイアウォールの設定を変更してリブートしておく。
sudo ufw allow 8080 # http for llama.cpp
sudo ufw status verbose
sudo reboot
テンプレートを指定してサーバを起動。
- https://github.com/ggerganov/llama.cpp/tree/master/examples/server
- https://github.com/ggerganov/llama.cpp/wiki/Templates-supported-by-llama_chat_apply_template
./server -ngl 0 -t 4 -c 8192 -m "models/gemma-2b-it.Q6_K.gguf" --chat-template "gemma" --port 8080 --host 0.0.0.0
ブラウザで http://pi5.local:8080
にアクセス。
gemmaの場合は以下のように設定。
- Prompt
- (空文字列)
- User name
user
- Bot name
model
- Prompt template
{{prompt}}
{{history}}
<start_of_turn>{{char}}
- Chat history template
<start_of_turn>{{name}}
{{message}}
<end_of_turn>
Say something...
のところに Summarize this text. (要約したい文章)
を入力して Send
ボタンを押すと model:
以下に返答が表示される。
Note: 次節以降の作業はローカルLLMには関係なく、Raspberry Piをサーバとして運用する際のよくある設定のメモ。
USBメモリ等の自動マウント
autofs
を使って、ディレクトリにアクセスしたタイミングで自動的にマウントして、一定時間アクセスがなければ自動的にアンマウントする。常にマウントするなら /etc/fstab
を使う。
USBメモリを挿して、blkid
を実行してデバイスのパスを確認しておく。e.g. /dev/sda1
ひとまず、手動でマウント出来るかどうか確認。
sudo mount -t vfat /dev/sda1 /mnt
ls -l /mnt
sudo umount /mnt
手動でマウント出来たら autofs
で自動マウント出来るようにする。
sudo apt-get install -y autofs
既に /media
ディレクトリが存在するので、そこに /dev/sda1
に挿された vfat の USB メモリを /media/usb0
に自動マウントすることにする。どこにマウントするべきかは以下ページ参照。
autofs の設定ファイルは /etc/auto.master
で、 man auto.master
でマニュアルが読める。設定方法には、/etc/auto.master
を直接編集する方法と、/etc/auto.master.d
ディレクトリにファイルを追加する方法がある。ここでは後者の方法で設定した。
マニュアルを読むと、/etc/auto.master.d/*.autofs
に追加のマウントポイントのファイルを置けと書いてあるので、media.autofs
というファイルを作成する。拡張子は *.autofs
である必要がある。
cat << EOF | sudo tee -a /etc/auto.master.d/media.autofs
/media /etc/auto.master.d/auto.media
EOF
さらに /etc/auto.master.d/auto.media
ファイルを作成する。
パラメータの意味は、fstype=vfat
でフォーマット指定。 iocharset=utf8,codepage=932
は日本語ファイル名の文字化け対応。uid=ota,gid=ota
はユーザ名を指定(指定しなくてもよいが指定がないとrootになる。ota
の部分は環境に合わせて変える)。dmask
と fmask
はマウントしたディレクトリとファイルのデフォルトのパーミッション設定(ディレクトリは755
、ファイルは644
にする)。
cat << EOF | sudo tee -a /etc/auto.master.d/auto.media
usb0 -fstype=vfat,iocharset=utf8,codepage=932,uid=ota,gid=ota,dmask=0022,fmask=0133 :/dev/sda1
EOF
autofs のデーモンを再起動(あるいは sudo reboot
でマシン再起動してもOK)。
sudo service autofs restart
/media/usb0
にアクセスしようとすると自動的にマウントされるはず。
ls -l /media/usb0
として中身のファイルが見られればOK。日本語ファイル名が文字化けしていないこと、ファイルの所有者とパーミッションを確認。また、df -h
でも確認。
スワップファイルの設定
Note: メモリ8GBモデルではおそらくスワップ不要
重いコンパイル等でメモリが足らなくなってフリーズしてしまうことがたまにあるので、その際はスワップを有効にする。コンパイル時だけ一時的にスワップ有効にして通常時はスワップ無効で運用する。
- https://help.ubuntu.com/community/SwapFaq
- https://www.digitalocean.com/community/tutorials/how-to-add-swap-space-on-ubuntu-20-04-ja
1g
が容量の指定。
cat /proc/swaps
ls -l /swapfile # 既にあれば別名にする
sudo fallocate -l 1g /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
cat /proc/swaps
一時的にスワップ有効にするのであればここまででOK。2回目以降は sudo swapon /swapfile
だけでOK。しばらく使わないなら sudo swapoff /swapfile && sudo rm /swapfile
で削除しておく。
もし、永続的に有効にするには /etc/fstab
に書き込んで再起動する。
sudo cp -p /etc/fstab /etc/fstab.bak
cat << EOF | sudo tee -a /etc/fstab
/swapfile swap swap defaults 0 0
EOF
Emacsのインストール
結構時間がかかる。
sudo apt-get update
sudo apt-get install -y emacs
以下の設定を追加。
mkdir -p ~/.emacs.d
cat >> ~/.emacs.d/init.el <<EOF
(set-language-environment "Japanese")
(setq inhibit-startup-message t)
(setq initial-scratch-message "")
(menu-bar-mode -1)
(tool-bar-mode 0)
EOF
Sambaのインストール
ファイル共有する場合はSambaをインストールする。
sudo apt-get install -y samba
sudo emacs /etc/samba/smb.conf
末尾に以下を追加。/media/usb0
は共有するディレクトリ。ota
はユーザ名。
[pi5usb0]
path = /media/usb0
read only = No
guest ok = Yes
force user = ota
ポートを開ける。
sudo ufw allow 139 # samba
sudo ufw allow 445 # samba
sudo ufw status verbose
sudo reboot
マシン再起動して、PCから smb://pi5.local
にアクセスしてゲストで読み書きできればOK。
Apacheのインストール
HTTPサーバを立てる場合はApacheをインストールする。
sudo apt-get install -y apache2
sudo cp -p /etc/apache2/sites-available/000-default.conf /etc/apache2/sites-available/default.conf
sudo emacs /etc/apache2/sites-available/default.conf
以下のように変更。
#DocumentRoot /var/www/html
DocumentRoot /media/usb0
<Directory /media/usb0/>
Options Indexes FollowSymLinks
AllowOverride None
Require all granted
</Directory>
変更を有効にする。
sudo a2dissite # 000-default を入力
sudo a2ensite # default を入力
sudo systemctl reload apache2
ポートを開ける。
sudo ufw allow 80 # http for apache
sudo ufw status verbose
sudo reboot
ブラウザで http://pi5.local
にアクセスして、USBメモリの中身が表示されればOK。
pyenvのインストール
Python開発環境が必要なら pyenv
をインストールする。
メモリが足りないとフリーズするので、一度試してみて、もしメモリが足りてなかったらスワップを有効にしてからインストールする。
curl https://pyenv.run | bash
echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bashrc
echo 'command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(pyenv init -)"' >> ~/.bashrc
シェルを開き直す。
pyenv install でコンパイルする際に cc
やライブラリが必要なのでインストールしておく。
sudo apt-get update
sudo apt-get install -y build-essential libssl-dev zlib1g-dev \
libbz2-dev libreadline-dev libsqlite3-dev curl \
libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev
Python をインストールする。数分かかる。
pyenv install --list
pyenv install 3.10.13 # 数分待つ
pyenv global 3.10.13
python -V
which python
which pip
pip list # pip と setuptools のみ
Poetryのインストール
Pythonのパッケージ管理にPoetryを使う場合。
curl -sSL https://install.python-poetry.org | python3 -
echo 'export PATH="/home/ota/.local/bin:$PATH"' >> ~/.bashrc
poetry --version
まとめ
- Raspberry Pi 5にUbuntuをヘッドレスでセットアップしてローカルLLMを動かす手順をまとめた
- Raspberry Pi 5 / 8GB では
gemma-7b-it
の2bit量子化版までは快適に動作 -
gemma-7b-it
の3bit以上の量子化版も試したが、メモリが足らず安定動作しなかった
以上
Discussion