大規模言語モデル(LLM)をMacbook Airで動かしてみた|Google Gemma
大規模言語モデルの1つGoogle GemmaをMacBook Air(M2, 16GB)で試してみました。本記事では、gemma-2bを動作させた際のパフォーマンスについて、実行結果を基にレポートします。試行錯誤の末、量子化を行うことでなんとか実用レベルで動作することが確認できました。具体的な実行手順とその結果を基に、MacBook Airでの大規模言語モデル(LLM)の実行可能性についてまとめました。
LLMの推論プロセスについては以下の記事でわかりやすく説明しています。気になる方は参考にしてください。
Gemmaとは
Gemmaは、2024年2月21日に発表されたGoogleが開発した大規模言語モデル(LLM)です。AI開発者や研究者による商用利用や再配布が可能なオープンソースとして提供されています。
llama.cppがgemmaにも対応したので早速動かしてみました。
結論としては、Macbook Air(M2, 16GB)で動作させるのは、結構厳しめという印象でした。
大規模言語モデル(LLM)をCUDAで動かす場合はこちらの記事が参考になると思います
M1/M2の性能に興味がある場合、Macbook Air(M2)でYOLOv8を動かした結果も参考になるかもしれません
手順
以下、Gemma-2bをダウンロードして動作させるまでの手順です。Gemma-2bをMacで動かすには、lamma.cppを利用します。
lamma.cppをダウンロード
githubからclone
lamma.cppをcloneします。
git clone https://github.com/ggerganov/llama.cpp
ダウンロードした、llama.cpp
のフォルダに移動してコンパイルします。
Macで動作させるには、LLAMA_METAL=1を加えてmakeしておきます。
cd llama.cpp
LLAMA_METAL=1 make
gemma-2bをダウンロード
IDとパスワードを聞かれたら、HuggingFaceのID(メールアドレス)と、パスワードのかわりにアクセストークンを入力。
アクセストークンは以下から入手します。
https://huggingface.co/settings/tokens
cd models
git clone https://huggingface.co/google/gemma-2b
動作テスト
gemma-2b
には、llama.cppで利用できるgemma-2b.ggufが含まれているので変換は必要はありません。
動作確認には、以下のコマンドを実行します
./main -m models/gemma-2b/gemma-2b.gguf -ngl 18 -n 64 -p "Who is Einstein?"
とりあえず、そのまま実行してみましたが、実行はかなり遅いです。上記のコマンドの実行では、出力が完了するまで10分くらいのかかりました。原因は、メモリ不足で一部レイヤーしかGPUで実行できなかったためだと思われます。
メモリ不足については4bit量子化されたモデルを使うことで解決できますが、そのまま動かすのは16GBしかメモリが搭載されていないMacbook Airでは無理なようです。
実行結果
% ./main -m models/gemma-2b/gemma-2b.gguf -ngl 18 -n 64 -p "Who is Einstein?"
Log start
main: build = 2297 (052051d8)
main: built with Apple clang version 15.0.0 (clang-1500.1.0.2.5) for arm64-apple-darwin23.3.0
main: seed = 1709296895
llama_model_loader: loaded meta data with 19 key-value pairs and 164 tensors from models/gemma-2b/gemma-2b.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 = gemma
llama_model_loader: - kv 1: general.name str = gemma-2b
llama_model_loader: - kv 2: gemma.context_length u32 = 8192
llama_model_loader: - kv 3: gemma.block_count u32 = 18
llama_model_loader: - kv 4: gemma.embedding_length u32 = 2048
llama_model_loader: - kv 5: gemma.feed_forward_length u32 = 16384
llama_model_loader: - kv 6: gemma.attention.head_count u32 = 8
llama_model_loader: - kv 7: gemma.attention.head_count_kv u32 = 1
llama_model_loader: - kv 8: gemma.attention.key_length u32 = 256
llama_model_loader: - kv 9: gemma.attention.value_length u32 = 256
llama_model_loader: - kv 10: gemma.attention.layer_norm_rms_epsilon f32 = 0.000001
llama_model_loader: - kv 11: tokenizer.ggml.model str = llama
llama_model_loader: - kv 12: tokenizer.ggml.bos_token_id u32 = 2
llama_model_loader: - kv 13: tokenizer.ggml.eos_token_id u32 = 1
llama_model_loader: - kv 14: tokenizer.ggml.padding_token_id u32 = 0
llama_model_loader: - kv 15: tokenizer.ggml.unknown_token_id u32 = 3
llama_model_loader: - kv 16: tokenizer.ggml.tokens arr[str,256128] = ["<pad>", "<eos>", "<bos>", "<unk>", ...
llama_model_loader: - kv 17: tokenizer.ggml.scores arr[f32,256128] = [0.000000, 0.000000, 0.000000, 0.0000...
llama_model_loader: - kv 18: tokenizer.ggml.token_type arr[i32,256128] = [3, 3, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, ...
llama_model_loader: - type f32: 164 tensors
llm_load_vocab: mismatch in special tokens definition ( 544/256128 vs 388/256128 ).
llm_load_print_meta: format = GGUF V3 (latest)
llm_load_print_meta: arch = gemma
llm_load_print_meta: vocab type = SPM
llm_load_print_meta: n_vocab = 256128
llm_load_print_meta: n_merges = 0
llm_load_print_meta: n_ctx_train = 8192
llm_load_print_meta: n_embd = 2048
llm_load_print_meta: n_head = 8
llm_load_print_meta: n_head_kv = 1
llm_load_print_meta: n_layer = 18
llm_load_print_meta: n_rot = 256
llm_load_print_meta: n_embd_head_k = 256
llm_load_print_meta: n_embd_head_v = 256
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-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 = 16384
llm_load_print_meta: n_expert = 0
llm_load_print_meta: n_expert_used = 0
llm_load_print_meta: pooling type = 0
llm_load_print_meta: rope type = 2
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 = 8192
llm_load_print_meta: rope_finetuned = unknown
llm_load_print_meta: model type = 2B
llm_load_print_meta: model ftype = all F32 (guessed)
llm_load_print_meta: model params = 2.51 B
llm_load_print_meta: model size = 9.34 GiB (32.00 BPW)
llm_load_print_meta: general.name = gemma-2b
llm_load_print_meta: BOS token = 2 '<bos>'
llm_load_print_meta: EOS token = 1 '<eos>'
llm_load_print_meta: UNK token = 3 '<unk>'
llm_load_print_meta: PAD token = 0 '<pad>'
llm_load_print_meta: LF token = 227 '<0x0A>'
llm_load_tensors: ggml ctx size = 0.13 MiB
ggml_backend_metal_buffer_from_ptr: allocated buffer, size = 7560.30 MiB, ( 7560.36 / 10922.67)
llm_load_tensors: offloading 18 repeating layers to GPU
llm_load_tensors: offloaded 18/19 layers to GPU
llm_load_tensors: Metal buffer size = 7560.29 MiB
llm_load_tensors: CPU buffer size = 2001.01 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
ggml_metal_init: allocating
ggml_metal_init: found device: Apple M2
ggml_metal_init: picking default device: Apple M2
ggml_metal_init: default.metallib not found, loading from source
ggml_metal_init: GGML_METAL_PATH_RESOURCES = nil
ggml_metal_init: loading '/Users/tadanori/llama2/llama.cpp/ggml-metal.metal'
ggml_metal_init: GPU name: Apple M2
ggml_metal_init: GPU family: MTLGPUFamilyApple8 (1008)
ggml_metal_init: GPU family: MTLGPUFamilyCommon3 (3003)
ggml_metal_init: GPU family: MTLGPUFamilyMetal3 (5001)
ggml_metal_init: simdgroup reduction support = true
ggml_metal_init: simdgroup matrix mul. support = true
ggml_metal_init: hasUnifiedMemory = true
ggml_metal_init: recommendedMaxWorkingSetSize = 11453.25 MB
ggml_backend_metal_buffer_type_alloc_buffer: allocated buffer, size = 9.00 MiB, ( 7571.17 / 10922.67)
llama_kv_cache_init: Metal KV buffer size = 9.00 MiB
llama_new_context_with_model: KV self size = 9.00 MiB, K (f16): 4.50 MiB, V (f16): 4.50 MiB
llama_new_context_with_model: CPU input buffer size = 6.01 MiB
ggml_backend_metal_buffer_type_alloc_buffer: allocated buffer, size = 77.02 MiB, ( 7648.19 / 10922.67)
llama_new_context_with_model: Metal compute buffer size = 77.00 MiB
llama_new_context_with_model: CPU compute buffer size = 508.25 MiB
llama_new_context_with_model: graph splits (measure): 3
system_info: n_threads = 4 / 8 | 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 = 1 | WASM_SIMD = 0 | BLAS = 1 | SSE3 = 0 | SSSE3 = 0 | VSX = 0 | MATMUL_INT8 = 0 |
sampling:
repeat_last_n = 64, repeat_penalty = 1.100, frequency_penalty = 0.000, presence_penalty = 0.000
top_k = 40, tfs_z = 1.000, top_p = 0.950, min_p = 0.050, typical_p = 1.000, temp = 0.800
mirostat = 0, mirostat_lr = 0.100, mirostat_ent = 5.000
sampling order:
CFG -> Penalties -> top_k -> tfs_z -> typical_p -> top_p -> min_p -> temperature
generate: n_ctx = 512, n_batch = 512, n_predict = 64, n_keep = 1
Who is Einstein?
1. His full name was Albert Einstein
2. He was born on March 14th, 1879 in Ulm, Germany
3. He died at the age of 66 on April 01st, <strong>1955</strong>
4. He married twice;
llama_print_timings: load time = 4348.82 ms
llama_print_timings: sample time = 37.85 ms / 64 runs ( 0.59 ms per token, 1691.02 tokens per second)
llama_print_timings: prompt eval time = 6321.34 ms / 5 tokens ( 1264.27 ms per token, 0.79 tokens per second)
llama_print_timings: eval time = 523694.68 ms / 63 runs ( 8312.61 ms per token, 0.12 tokens per second)
llama_print_timings: total time = 530167.11 ms / 68 tokens
ggml_metal_free: deallocating
Log end
Gemma-2bの返答だけを抜き出すと以下になります。返答自体は2bのモデルでも悪くあrません。ただ、誕生日と死亡年月日はあっていますが、年齢は66歳ではなく76歳なので間違っていました。
Who is Einstein?
1. His full name was Albert Einstein
2. He was born on March 14th, 1879 in Ulm, Germany
3. He died at the age of 66 on April 01st, <strong>1955</strong>
興味本位でMacbook Airで動かしてみましたが、残念な結果となりました。この速度なら、Google Colabで動作させたほうが快適です。
メモリ16GBしかないMacbook AirではLLMを動作させるのは厳しいようです。
今回、速度が遅かった理由は、メモリ不足で一部のレイヤーをCPUで実行しているためです。メモリがもっと多ければ、もう少し普通に動いたと思います。16GBだと、ブラウザなどが開いているとメモリが厳しい感じです。MacでLLMを動かしたい場合はなるべく、メモリを多く搭載した方がよいと思います。
4ビット量子化バージョンで実行
メモリ不足で動作が遅い場合は、少ないビット数で量子化されたバージョンのモデルを利用することで解決することがあります。
HuggingFaceに4bitに量子化したバージョンも公開されていましたので、そちらも試してみました。
量子化したバージョン:https://huggingface.co/mlabonne/gemma-2b-GGUF
なお、いくつかの量子化違いのバージョンがありました。今回は2つを試してみました。
gemma-2b.Q4_0.gguf
"q4_0
: Original quant method, 4-bit.”と説明に書かれたバージョンです。4bitに量子化されたバージョンで、gemma-2b/gemma-2b.gguf
が10GBに対して1.42GBと非常に小さくなっています。
実行には、以下のコマンドを用いました。GPUで全ての層を実行できるので、-ngl
オプションは必要ありません。
./main -m gemma-2b.Q4_0.gguf -n 64 -p "Who is Einstein?"
実行すると、以下のような結果が出力されました。出力はかなり高速で、下記の出力が完了するまで3.8秒でした。
Who is Einstein?
apprehendient of apprehendient apprehendient apprehendient apprehendient apprehendient apprehendient apprehendient apprehendient apprehendient apprehendient apprehendient apprehendient apprehendient apprehendient apprehendient apprehendient apprehendient apprehendient apprehendient apprehendient apprehendient apprehendient apprehendient apprehendient apprehendient apprehendient apprehendient apprehendient apprehendient apprehendient
速度は速いですが、結果は上のように全然ダメでした。
gemma-2b.Q8_0.gguf
もう1つは、量子化されたモデルの中で最も大きなモデルです。説明には"q8_0: Almost indistinguishable from float16. High resource use and slow. Not recommended for most users."
と書かれています。サイズは2.67GBと大きいですが、10GBからは随分小さくなっています。
実行コマンドは以下になります。
./main -m gemma-2b.Q8_0.gguf -n 64 -p "Who is Einstein?"
こちらの出力は以下の通りです。出力に要した時間は7.8秒でした。こちらは、一応それっぽい回答が戻ってきました。
Who is Einstein?
Einstein’s most well-known achievement was his theory of relativity. This theory was developed by Albert Einstein, an Austrian-born theoretical physicist and mathematician.
Einstein’s theory of relativity is a fundamental theory of physics that provides a unified description of the fundamental forces of nature. It describes how gravity, electromagnetism
ただ、生まれが間違っていました。また、後半の文章(下線部)も怪しいです。
結果としては、「量子化すれば動かせるが、量子化により出力結果がおかしくなることがある」という感じでした。
ドキュメントには、Not recommended for most users.
と書かれていますが、この文章が8秒くらいで出力されるのであれば、gemma-2b.Q8_0.gguf
を使ってもよいのではないかと感じました。
llama.cppのベンチマークを見る限り、量子化しない方が早いみたいなので、メモリがあれば量子化しないモデルの方が速いと思われます
おまけ(gemma-7bの量子化バージョンを動かす)
gemma-2b
の量子化バージョンがサクサクうごいたので、gemma-7b
の量子化版も動かしてみました。こちらは、70億パラメータのモデルで2bに比べて3倍以上大規模なモデルとなります。
以下のリポジトリから、gemma-7b-it.Q4_0.gguf
をダウンロードして試してみました。
量子化したバージョン:https://huggingface.co/mlabonne/gemma-7b-it-GGUF
容量は、5.04GBで、gemma-2b.gguf
より小さいです。
今回は、-it
のついたモデルなので、質問・回答の形でプロンプトを入力しています。
./main -m gemma-7b-it.Q4_0.gguf -p "<start_of_turn>user\nWho is Einstein?<end_of_turn>\nmodel\n" -n 64
動かした結果が以下になります。メモリ不足にならなかったみたいで、処理時間は12.4秒と結構高速でした。
量子化した7Bの方が2Bより賢そうなので、7Bを量子化したものを動かした方がよいかもしれません。
<start_of_turn>user
Who is Einstein?<end_of_turn>
model
Albert Einstein was a German-born theoretical physicist who revolutionized scientific understanding with his theories on relativity. Born in 1879, Einstein was a precocious child who struggled with conventional schooling but excelled in mathematics and physics.
**Key Contributions:**
* **Theory of Relativity:** In 1905
なぜ、GPUのメモリ不足になるのか。
MacはCPUとGPUは同じメモリを共有するので、「なぜCPUで動かせるのにGPUのメモリが不足するの?」と思った方もいるかもしれません。どうも、GPUに割り当てることができるメモリ容量には制限があるようで、16GBのすべてをGPUに割り当てることができるわけではないようです。このため、モデル自身がメモリに入っても、GPUに割り当てることができる上限のサイズを超えた場合は、エラーとなってしまうようです
まとめ
Macbook AirでGoogle Gemmaを興味本意で動作させてみました。2Bならなんとかなるかと思いましたが、動作することは動作するけどかなり実行速度が遅いという結果でした。
また、量子化すれば実用できるレベルで出力できることが確認できました。ただ、回答がおかしくなることも同時に確認できました。
量子化した7bモデルが2bモデルより小さく応答結果も良かったので、MacBook Airの16GBモデルで動作させる場合は、量子化された7bモデルを動かすのが一番良いのかもしれません。
とりあえず、モバイルノートでLLMが実行できるのは興味深いです。