hacomono TECH BLOG

フィットネスクラブ・スクールなど施設・店舗のための会員管理・予約・決済システム「hacomono」 開発チームの技術ブログ

Raspberry Pi 5の基本セットアップとLLM動かしてみた

はじめに

hacomonoのIoT部マネージャーの岩貞(さとちゃん)です。
最近hacomonoの影響もあってか、サウナにちょっとずつハマっています。
友人の結婚祝いで名古屋に行くことがあったので SENSE sauna 行ってきました。大衆浴場のサウナしか行ったことがなかったので、小スペースで友人だけと会話しながら入れるサウナはすごく良かったです。
良いサウナ場をこれからも探そうと思います笑


今回の話

技適がついに通ったラズパイ5。少し静観してたのですが、スイッチサイエンスに4GBモデルの在庫があるということでIoTのSW開発メンバーを中心に研究開発目的で購入してお配りしました。

あわせて自分も手元にGETしたので、眠らせるのはもったいないということで「火入れだけでも…」と思い動かしてみました。

その時の簡単なセットアップ手順とついでに流行りっぽいもの動かしてみました。


環境

Raspberry Pi 5 4GBモデル
安定のスイッチサイエンスさんから買いました。
Raspberry Pi 5 / 4GBwww.switch-science.com

電源は5V3Aでも動作するということでラズパイ4のものを流用。
ケースもなし、ファンやヒートシンクもなしです。

電源はまだ専用のものが用意できていないというのと、ケースやファンも売り切れていたので致し方なし。


イメージ書き込み

  • 適当なSD見繕う (過去使ってたものを見つけてきた)
  • Raspberry Pi Imagerでラズパイ5向けのイメージを書き込み
    • 昔はSDへのイメージ書き込みも面倒だったけど、今はImagerで一発。脳死で行ける(便利な世界)
    • 書き込み前に色々事前に設定出来るが、自宅のWiFi設定をしておくのとSSHの設定をしておくのをおすすめする。
      • キーボードやら、マウスやら、ディスプレイやらを引っ張ってこなくてもターミナル入ってあれこれ出来る
      • 色々準備すると机の上がとっ散らかるのでとても大事。

ラズパイ5のセットアップ

  • SDに書き込めたらラズパイ5に挿して、電源ON
  • 起動したらSSHで接続
$ ssh pi@raspberrypi.local
  • ラズパイ5の証を確認しておく。
pi@raspberrypi:~ $ lscpu
Architecture:            aarch64
  CPU op-mode(s):        32-bit, 64-bit
  Byte Order:            Little Endian
CPU(s):                  4
  On-line CPU(s) list:   0-3
Vendor ID:               ARM
  Model name:            Cortex-A76
    Model:               1
    Thread(s) per core:  1
    Core(s) per cluster: 4
    Socket(s):           -
    Cluster(s):          1
    Stepping:            r4p1
    CPU(s) scaling MHz:  62%
    CPU max MHz:         2400.0000
    CPU min MHz:         1500.0000
    BogoMIPS:            108.00
    Flags:               fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm lrcpc dcpop asimddp
Vulnerabilities:
  Gather data sampling:  Not affected
  Itlb multihit:         Not affected
  L1tf:                  Not affected
  Mds:                   Not affected
  Meltdown:              Not affected
  Mmio stale data:       Not affected
  Retbleed:              Not affected
  Spec rstack overflow:  Not affected
  Spec store bypass:     Mitigation; Speculative Store Bypass disabled via prctl
  Spectre v1:            Mitigation; __user pointer sanitization
  Spectre v2:            Mitigation; CSV2, BHB
  Srbds:                 Not affected
  Tsx async abort:       Not affected

うん、Cortex-A76だ。間違いない。

  • SWアップデートしておく
$ sudo apt update 
$ sudo apt upgrade
$ reboot #念の為reboot

以上、特にこだわらなければセットアップはこれで完了。


生成AI動かしてみる

ここまで来たら後は良しなに。

何しようかなと思って他の方のラズパイ5の投稿見つつ最近さわれてなかったLLM動かすのが面白そうだったので本当に書いてあるとおりに動かす。 qiita.com

# Python仮想環境作る
$ python -m venv llama2
$ cd llama2
$ . bin/activate # ついでに環境も切り替えておく

# モデルをDL
$ wget https://huggingface.co/TheBloke/TinyLlama-1.1B-Chat-v1.0-GGUF/resolve/main/tinyllama-1.1b-chat-v1.0.Q4_K_M.gguf

※ここでvimもinstallしておく

$ sudo apt install neovim

記事記載の通りにvimとかでファイル作成する。

$ vim llama2.py

↓中身

import sys
from llama_cpp import Llama
llm = Llama(model_path="tinyllama-1.1b-chat-v1.0.Q4_K_M.gguf")
print(llm("<user>\n" + sys.argv[1] + "\n<assistant>\n", max_tokens=40)['choices'][0]["text"] + "...")

これだけで準備完了。
動かしてみる。

(llama2) pi@raspberrypi:~/llama2 $ python llama2.py "what is Empire State Building?"
llama_model_loader: loaded meta data with 23 key-value pairs and 201 tensors from tinyllama-1.1b-chat-v1.0.Q4_K_M.gguf (version GGUF V3 (latest))
llama_model_loader: Dumping metadata keys/values. Note: KV overrides do not apply in this output.
llama_model_loader: - kv   0:                       general.architecture str              = llama
llama_model_loader: - kv   1:                               general.name str              = tinyllama_tinyllama-1.1b-chat-v1.0
llama_model_loader: - kv   2:                       llama.context_length u32              = 2048
llama_model_loader: - kv   3:                     llama.embedding_length u32              = 2048
llama_model_loader: - kv   4:                          llama.block_count u32              = 22
llama_model_loader: - kv   5:                  llama.feed_forward_length u32              = 5632
llama_model_loader: - kv   6:                 llama.rope.dimension_count u32              = 64
llama_model_loader: - kv   7:                 llama.attention.head_count u32              = 32
llama_model_loader: - kv   8:              llama.attention.head_count_kv u32              = 4
llama_model_loader: - kv   9:     llama.attention.layer_norm_rms_epsilon f32              = 0.000010
llama_model_loader: - kv  10:                       llama.rope.freq_base f32              = 10000.000000
llama_model_loader: - kv  11:                          general.file_type u32              = 15
llama_model_loader: - kv  12:                       tokenizer.ggml.model str              = llama
llama_model_loader: - kv  13:                      tokenizer.ggml.tokens arr[str,32000]   = ["<unk>", "<s>", "</s>", "<0x00>", "<...
llama_model_loader: - kv  14:                      tokenizer.ggml.scores arr[f32,32000]   = [0.000000, 0.000000, 0.000000, 0.0000...
llama_model_loader: - kv  15:                  tokenizer.ggml.token_type arr[i32,32000]   = [2, 3, 3, 6, 6, 6, 6, 6, 6, 6, 6, 6, ...
llama_model_loader: - kv  16:                      tokenizer.ggml.merges arr[str,61249]   = ["▁ t", "e r", "i n", "▁ a", "e n...
llama_model_loader: - kv  17:                tokenizer.ggml.bos_token_id u32              = 1
llama_model_loader: - kv  18:                tokenizer.ggml.eos_token_id u32              = 2
llama_model_loader: - kv  19:            tokenizer.ggml.unknown_token_id u32              = 0
llama_model_loader: - kv  20:            tokenizer.ggml.padding_token_id u32              = 2
llama_model_loader: - kv  21:                    tokenizer.chat_template str              = {% for message in messages %}\n{% if m...
llama_model_loader: - kv  22:               general.quantization_version u32              = 2
llama_model_loader: - type  f32:   45 tensors
llama_model_loader: - type q4_K:  135 tensors
llama_model_loader: - type q6_K:   21 tensors
llm_load_vocab: special tokens definition check successful ( 259/32000 ).
llm_load_print_meta: format           = GGUF V3 (latest)
llm_load_print_meta: arch             = llama
llm_load_print_meta: vocab type       = SPM
llm_load_print_meta: n_vocab          = 32000
llm_load_print_meta: n_merges         = 0
llm_load_print_meta: n_ctx_train      = 2048
llm_load_print_meta: n_embd           = 2048
llm_load_print_meta: n_head           = 32
llm_load_print_meta: n_head_kv        = 4
llm_load_print_meta: n_layer          = 22
llm_load_print_meta: n_rot            = 64
llm_load_print_meta: n_embd_head_k    = 64
llm_load_print_meta: n_embd_head_v    = 64
llm_load_print_meta: n_gqa            = 8
llm_load_print_meta: n_embd_k_gqa     = 256
llm_load_print_meta: n_embd_v_gqa     = 256
llm_load_print_meta: f_norm_eps       = 0.0e+00
llm_load_print_meta: f_norm_rms_eps   = 1.0e-05
llm_load_print_meta: f_clamp_kqv      = 0.0e+00
llm_load_print_meta: f_max_alibi_bias = 0.0e+00
llm_load_print_meta: n_ff             = 5632
llm_load_print_meta: n_expert         = 0
llm_load_print_meta: n_expert_used    = 0
llm_load_print_meta: rope scaling     = linear
llm_load_print_meta: freq_base_train  = 10000.0
llm_load_print_meta: freq_scale_train = 1
llm_load_print_meta: n_yarn_orig_ctx  = 2048
llm_load_print_meta: rope_finetuned   = unknown
llm_load_print_meta: model type       = 1B
llm_load_print_meta: model ftype      = Q4_K - Medium
llm_load_print_meta: model params     = 1.10 B
llm_load_print_meta: model size       = 636.18 MiB (4.85 BPW)
llm_load_print_meta: general.name     = tinyllama_tinyllama-1.1b-chat-v1.0
llm_load_print_meta: BOS token        = 1 '<s>'
llm_load_print_meta: EOS token        = 2 '</s>'
llm_load_print_meta: UNK token        = 0 '<unk>'
llm_load_print_meta: PAD token        = 2 '</s>'
llm_load_print_meta: LF token         = 13 '<0x0A>'
llm_load_tensors: ggml ctx size =    0.08 MiB
llm_load_tensors:        CPU buffer size =   636.18 MiB
..................................................................................
llama_new_context_with_model: n_ctx      = 512
llama_new_context_with_model: freq_base  = 10000.0
llama_new_context_with_model: freq_scale = 1
llama_kv_cache_init:        CPU KV buffer size =    11.00 MiB
llama_new_context_with_model: KV self size  =   11.00 MiB, K (f16):    5.50 MiB, V (f16):    5.50 MiB
llama_new_context_with_model:        CPU input buffer size   =     6.01 MiB
llama_new_context_with_model:        CPU compute buffer size =    66.50 MiB
llama_new_context_with_model: graph splits (measure): 1
AVX = 0 | AVX_VNNI = 0 | AVX2 = 0 | AVX512 = 0 | AVX512_VBMI = 0 | AVX512_VNNI = 0 | FMA = 0 | NEON = 1 | ARM_FMA = 1 | F16C = 0 | FP16_VA = 0 | WASM_SIMD = 0 | BLAS = 0 | SSE3 = 0 | SSSE3 = 0 | VSX = 0 | MATMUL_INT8 = 0 |
Model metadata: {'tokenizer.chat_template': "{% for message in messages %}\n{% if message['role'] == 'user' %}\n{{ '<|user|>\n' + message['content'] + eos_token }}\n{% elif message['role'] == 'system' %}\n{{ '<|system|>\n' + message['content'] + eos_token }}\n{% elif message['role'] == 'assistant' %}\n{{ '<|assistant|>\n'  + message['content'] + eos_token }}\n{% endif %}\n{% if loop.last and add_generation_prompt %}\n{{ '<|assistant|>' }}\n{% endif %}\n{% endfor %}", 'tokenizer.ggml.padding_token_id': '2', 'tokenizer.ggml.unknown_token_id': '0', 'tokenizer.ggml.eos_token_id': '2', 'general.architecture': 'llama', 'llama.rope.freq_base': '10000.000000', 'llama.context_length': '2048', 'general.name': 'tinyllama_tinyllama-1.1b-chat-v1.0', 'llama.embedding_length': '2048', 'llama.feed_forward_length': '5632', 'llama.attention.layer_norm_rms_epsilon': '0.000010', 'llama.rope.dimension_count': '64', 'tokenizer.ggml.bos_token_id': '1', 'llama.attention.head_count': '32', 'llama.block_count': '22', 'llama.attention.head_count_kv': '4', 'general.quantization_version': '2', 'tokenizer.ggml.model': 'llama', 'general.file_type': '15'}
Using chat template: {% for message in messages %}
{% if message['role'] == 'user' %}
{{ '<|user|>
' + message['content'] + eos_token }}
{% elif message['role'] == 'system' %}
{{ '<|system|>
' + message['content'] + eos_token }}
{% elif message['role'] == 'assistant' %}
{{ '<|assistant|>
'  + message['content'] + eos_token }}
{% endif %}
{% if loop.last and add_generation_prompt %}
{{ '<|assistant|>' }}
{% endif %}
{% endfor %}
Using chat eos_token:
Using chat bos_token:

llama_print_timings:        load time =    1366.39 ms
llama_print_timings:      sample time =      10.24 ms /    40 runs   (    0.26 ms per token,  3907.78 tokens per second)
llama_print_timings: prompt eval time =    1366.32 ms /    17 tokens (   80.37 ms per token,    12.44 tokens per second)
llama_print_timings:        eval time =    3326.60 ms /    39 runs   (   85.30 ms per token,    11.72 tokens per second)
llama_print_timings:       total time =    4850.41 ms /    56 tokens
The Empire State Building, also known as the Freedom Tower or simply "the Blob," is a skyscraper located in New York City, USA. It was completed in 19...

色々でますが、一番最後のところの

The Empire State Building, also known as the Freedom Tower or simply "the Blob," is a skyscraper located in New York City, USA. It was completed in 19...

が実行結果。

tokenサイズを40としているので途中で切れている。
40くらいだと数秒で実行結果が出力されましたが、これを400とか10倍にすると数十秒待つ必要がありました。

max_token増やすとすごい時間かかる。あと、ChatGPTと違って最後にバッと出力されるので、体感結構かかる気がした。途中経過を生成している様を見るのは結構体験的にも大事なのかも知れない。そこはllmの出力optionでなんとかできそうな気もしたけど一旦おいとく。

今使ってるモデルは英語オンリーなので、日本語使えるモデルで置き換えてみた。
以下から適当なものを取得。 huggingface.co

サンプルのggufのモデル指定部分を置き換えてみる。

$ vim llama2_jp.py

↓中身はコピペしてモデル部分だけ置き換えた

import sys
from llama_cpp import Llama
llm = Llama(model_path="/home/pi/llama2/ELYZA-japanese-Llama-2-7b-fast-instruct-q2_K.gguf")
print(llm("<user>\n" + sys.argv[1] + "\n<assistant>\n", max_tokens=40)['choices'][0]["text"] + "...")

実行してみる。その時、親子丼食べたかったんで親子丼について聞いてみた。

(llama2) pi@raspberrypi:~/llama2 $ python llama2_jp.py "親子丼のこと教えて"
llama_model_loader: loaded meta data with 21 key-value pairs and 291 tensors from /home/pi/llama2/ELYZA-japanese-Llama-2-7b-fast-instruct-q2_K.gguf (version GGUF V3 (latest))
llama_model_loader: Dumping metadata keys/values. Note: KV overrides do not apply in this output.
llama_model_loader: - kv   0:                       general.architecture str              = llama
llama_model_loader: - kv   1:                               general.name str              = ELYZA-japanese-Llama-2-7b-fast-instruct
llama_model_loader: - kv   2:      general.source.huggingface.repository str              = elyza/ELYZA-japanese-Llama-2-7b-fast-...
llama_model_loader: - kv   3:                   llama.tensor_data_layout str              = Meta AI original pth
llama_model_loader: - kv   4:                       llama.context_length u32              = 4096
llama_model_loader: - kv   5:                     llama.embedding_length u32              = 4096
llama_model_loader: - kv   6:                          llama.block_count u32              = 32
llama_model_loader: - kv   7:                  llama.feed_forward_length u32              = 11008
llama_model_loader: - kv   8:                 llama.rope.dimension_count u32              = 128
llama_model_loader: - kv   9:                 llama.attention.head_count u32              = 32
llama_model_loader: - kv  10:              llama.attention.head_count_kv u32              = 32
llama_model_loader: - kv  11:     llama.attention.layer_norm_rms_epsilon f32              = 0.000001
llama_model_loader: - kv  12:                       tokenizer.ggml.model str              = llama
llama_model_loader: - kv  13:                      tokenizer.ggml.tokens arr[str,45043]   = ["<unk>", "<s>", "</s>", "<0x00>", "<...
llama_model_loader: - kv  14:                      tokenizer.ggml.scores arr[f32,45043]   = [0.000000, 0.000000, 0.000000, 0.0000...
llama_model_loader: - kv  15:                  tokenizer.ggml.token_type arr[i32,45043]   = [2, 3, 3, 6, 6, 6, 6, 6, 6, 6, 6, 6, ...
llama_model_loader: - kv  16:                tokenizer.ggml.bos_token_id u32              = 1
llama_model_loader: - kv  17:                tokenizer.ggml.eos_token_id u32              = 2
llama_model_loader: - kv  18:            tokenizer.ggml.unknown_token_id u32              = 0
llama_model_loader: - kv  19:               general.quantization_version u32              = 2
llama_model_loader: - kv  20:                          general.file_type u32              = 10
llama_model_loader: - type  f32:   65 tensors
llama_model_loader: - type q2_K:   65 tensors
llama_model_loader: - type q3_K:  160 tensors
llama_model_loader: - type q6_K:    1 tensors
llm_load_vocab: mismatch in special tokens definition ( 304/45043 vs 264/45043 ).
llm_load_print_meta: format           = GGUF V3 (latest)
llm_load_print_meta: arch             = llama
llm_load_print_meta: vocab type       = SPM
llm_load_print_meta: n_vocab          = 45043
llm_load_print_meta: n_merges         = 0
llm_load_print_meta: n_ctx_train      = 4096
llm_load_print_meta: n_embd           = 4096
llm_load_print_meta: n_head           = 32
llm_load_print_meta: n_head_kv        = 32
llm_load_print_meta: n_layer          = 32
llm_load_print_meta: n_rot            = 128
llm_load_print_meta: n_embd_head_k    = 128
llm_load_print_meta: n_embd_head_v    = 128
llm_load_print_meta: n_gqa            = 1
llm_load_print_meta: n_embd_k_gqa     = 4096
llm_load_print_meta: n_embd_v_gqa     = 4096
llm_load_print_meta: f_norm_eps       = 0.0e+00
llm_load_print_meta: f_norm_rms_eps   = 1.0e-06
llm_load_print_meta: f_clamp_kqv      = 0.0e+00
llm_load_print_meta: f_max_alibi_bias = 0.0e+00
llm_load_print_meta: n_ff             = 11008
llm_load_print_meta: n_expert         = 0
llm_load_print_meta: n_expert_used    = 0
llm_load_print_meta: rope scaling     = linear
llm_load_print_meta: freq_base_train  = 10000.0
llm_load_print_meta: freq_scale_train = 1
llm_load_print_meta: n_yarn_orig_ctx  = 4096
llm_load_print_meta: rope_finetuned   = unknown
llm_load_print_meta: model type       = 7B
llm_load_print_meta: model ftype      = Q2_K - Medium
llm_load_print_meta: model params     = 6.85 B
llm_load_print_meta: model size       = 2.69 GiB (3.37 BPW)
llm_load_print_meta: general.name     = ELYZA-japanese-Llama-2-7b-fast-instruct
llm_load_print_meta: BOS token        = 1 '<s>'
llm_load_print_meta: EOS token        = 2 '</s>'
llm_load_print_meta: UNK token        = 0 '<unk>'
llm_load_print_meta: LF token         = 13 '<0x0A>'
llm_load_tensors: ggml ctx size =    0.11 MiB
llm_load_tensors:        CPU buffer size =  2752.83 MiB
..............................................................................................
llama_new_context_with_model: n_ctx      = 512
llama_new_context_with_model: freq_base  = 10000.0
llama_new_context_with_model: freq_scale = 1
llama_kv_cache_init:        CPU KV buffer size =   256.00 MiB
llama_new_context_with_model: KV self size  =  256.00 MiB, K (f16):  128.00 MiB, V (f16):  128.00 MiB
llama_new_context_with_model:        CPU input buffer size   =    10.01 MiB
llama_new_context_with_model:        CPU compute buffer size =    95.97 MiB
llama_new_context_with_model: graph splits (measure): 1
AVX = 0 | AVX_VNNI = 0 | AVX2 = 0 | AVX512 = 0 | AVX512_VBMI = 0 | AVX512_VNNI = 0 | FMA = 0 | NEON = 1 | ARM_FMA = 1 | F16C = 0 | FP16_VA = 0 | WASM_SIMD = 0 | BLAS = 0 | SSE3 = 0 | SSSE3 = 0 | VSX = 0 | MATMUL_INT8 = 0 |
Model metadata: {'general.file_type': '10', 'tokenizer.ggml.unknown_token_id': '0', 'tokenizer.ggml.eos_token_id': '2', 'general.architecture': 'llama', 'llama.context_length': '4096', 'general.name': 'ELYZA-japanese-Llama-2-7b-fast-instruct', 'general.source.huggingface.repository': 'elyza/ELYZA-japanese-Llama-2-7b-fast-instruct', 'llama.embedding_length': '4096', 'llama.tensor_data_layout': 'Meta AI original pth', 'llama.feed_forward_length': '11008', 'llama.attention.layer_norm_rms_epsilon': '0.000001', 'llama.rope.dimension_count': '128', 'tokenizer.ggml.bos_token_id': '1', 'llama.attention.head_count': '32', 'llama.block_count': '32', 'llama.attention.head_count_kv': '32', 'general.quantization_version': '2', 'tokenizer.ggml.model': 'llama'}

llama_print_timings:        load time =   12159.14 ms
llama_print_timings:      sample time =      13.89 ms /    40 runs   (    0.35 ms per token,  2880.18 tokens per second)
llama_print_timings: prompt eval time =   12159.06 ms /    16 tokens (  759.94 ms per token,     1.32 tokens per second)
llama_print_timings:        eval time =   30298.97 ms /    39 runs   (  776.90 ms per token,     1.29 tokens per second)
llama_print_timings:       total time =   42718.35 ms /    55 tokens
ごちそうが好きなので親子丼を食べたことがあります。
赤ちゃんがまだ小学生のときに食べて、とても美味しかったですね。
子供の頃は今ほど親に...

一番下部に回答がありますね。

ちなみにはじめ動かしていたモデルの tinyllama-1.1b-chat-v1.0.Q4_K_M.gguf は638MBだったのですが、持ってきた日本語モデル ELYZA-japanese-Llama-2-7b-fast-instruct-q2_K.gguf は2.7GBもあるせいか、読み込みがめっちゃ遅い。SDのI/O遅いというのもあるかも。。
その分実行結果もめちゃくちゃ遅いです。メモリ8GBだともう少し頑張れたのかも。

ちなみに参考にした記事をベースにNode-RED使ったchatサーバーまでやりました。
同じようにやってみたい方はパス設定で動かなかったりしたのでそこだけ注意してください。 結果はまぁ… 笑


さいごに

ラズパイ5セットアップついでにLLM動かせたのはなんか良かった。でも4からの比較にはならなかったです。そこはやってる人多いからもういいよね…?

ラズパイ5が技適が通ってちょうど各方面、手に入れられているようで色々と記事が増えてきて面白いです。
個人的にはSSD周りも簡単にできるようになっているので高速化のためにやってみたいですね。 note.com

Interfaceも特集記事上げるみたいなのでちょっと祭り的に盛り上がるの良いですね。業界盛り上げるようなデバイスが最近なかった気がして嬉しい。

宣伝

最近、当社で顧問としても入っていただいているくずさんとの対談記事が公開されたので、あわせて見ていただけるとhacomonoのIoTが結構頑張ってるところがわかると思います笑




株式会社hacomonoでは一緒に働く仲間を募集しています。
採用情報や採用ウィッシュリストもぜひご覧ください!