🤖

Raspberry Pi 5にUbuntuをヘッドレスでセットアップしてローカルLLMを動かす

2024/03/27に公開

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)

qa by llama.cpp with gemma-2b-it

ハードウェア

  • 本体: 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

テンプレートを指定してサーバを起動。

./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: 以下に返答が表示される。

summarize text by llama.cpp with gemma-2b-it

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 の部分は環境に合わせて変える)。dmaskfmask はマウントしたディレクトリとファイルのデフォルトのパーミッション設定(ディレクトリは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モデルではおそらくスワップ不要

重いコンパイル等でメモリが足らなくなってフリーズしてしまうことがたまにあるので、その際はスワップを有効にする。コンパイル時だけ一時的にスワップ有効にして通常時はスワップ無効で運用する。

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以上の量子化版も試したが、メモリが足らず安定動作しなかった

以上

GitHubで編集を提案

Discussion