Googleによる生成AIモデル「Gemma 2」をMacBook(M2)で動かしてみた

[Supership](https://supership.jp/)の名畑です。2024年夏アニメが始まりましたが[異世界スーサイド・スクワッド](https://suicidesquad-isekai.com/ja/)がエンタメに徹していて見ていて楽しい。ハーレイ・クインはやっぱり最強。

## はじめに

Googleによる生成AIモデル[Gemma](https://blog.google/technology/developers/gemma-open-models/)の後継である[Gemma 2](https://blog.google/technology/developers/google-gemma-2/)が先日リリースされました。

> 　2月に発表した「Gemma」は20億パラメータと70億パラメータの2サイズでの展開だったが、Gemma 2は90億パラメータと270億パラメータの2サイズ。
>　270億パラメータ版は、「そのサイズクラスで最高の性能を発揮し、2倍以上の規模のモデルに匹敵する性能を実現」するという。90億パラメータ版でも、[米Metaの「Llama 3」](https://www.itmedia.co.jp/news/articles/2404/19/news095.html)の80億パラメータ版などを上回る性能を示すとしている。
>
> 参考：[Google、オープンな生成AIモデル「Gemma 2」リリース　270億パラメータモデル追加 - ITmedia NEWS](https://www.itmedia.co.jp/news/articles/2406/28/news114.html)

**Gemma 2**を手元のMacBook(M2)で動かした過程を記事に残します。

2024年2月に書いた記事「[Googleによる生成AIモデル「Gemma」をMacBook(M2)で動かしてみた](https://qiita.com/nabata/items/01cf907c289db3fdb47d)」の最新版です。

コードはすべて**Python**です。

## 私の環境

PCは[MacBook Pro(Apple M2 Proチップ)](https://support.apple.com/kb/SP889?locale=ja_JP)です。
OSは[macOS 14 Sonoma](https://www.apple.com/jp/macos/sonoma/)です。

[accelerate](https://github.com/huggingface/accelerate)、[torch](https://pytorch.org/)、[transformers](https://huggingface.co/docs/transformers/index)を入れておきます。

```shell-session
$ pip install accelerate torch transformers        
```

私の環境でのバージョンはそれぞれ**0.32.1**、**2.3.1**、**4.42.4**でした。

```shell-session
$ pip list | grep -e accelerate -e torch -e transformers
accelerate         0.32.1
torch              2.3.1
transformers       4.42.4
```

## モデル

**gemma-27b**(270億パラメータ)と**Gemma 9b**(90億パラメータ)があります。そしてそれぞれに**instruct-tuned**(指示調整)されたものが用意されています。

- [gemma-2-27b-it](https://huggingface.co/google/gemma-2-27b-it)
- [gemma-2-27b](https://huggingface.co/google/gemma-2-27b)
- [gemma-2-9b-it](https://huggingface.co/google/gemma-2-9b-it)
- [gemma-2-9b](https://huggingface.co/google/gemma-2-9b)


本記事では**gemma-2-27b-it**を用いて話を進めていますが、初回の実行時は**約54.5GB**のモデルをダウンロードする必要があります。さらにコードの実行にも時間がかかります。

そのため、動かすこと自体が目的であれば**gemma-2-9b-it**で進めていただければと思います。
各コードに2箇所ある**gemma-2-27b-it**を**gemma-2-9b-it**に置き換えるだけです。
コード例も本記事の最後に記載しております。

## ライセンスの認証

今回は[Hugging Face](https://huggingface.co/)経由でモデルを入手して実行します。
そのためにまずは**Hugging Face**のアカウントを作成します。

**Gemma**と**Gemma 2**は同一権限のため、**Gemma**ですでに権限付与済みの場合はこの手順は不要です。また、モデル毎に権限を取得し直す必要はありません。

アカウントを作成してログイン後、先述したモデルのいずれかのページに行くと**Gemma**へのアクセスのためのライセンス承認が求められます。

![gemma_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/d5549f57-8a82-8f50-34ec-b4624a403957.png)

**Acknowledge license**を押します。

![gemma_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/affb1508-1df3-6f04-566d-449bd7f1aec7.png)

**Access Request**のページに遷移しますので、フォームに名前等を記入していきます。
規約も読んで最後の**Accept**を押します。

モデルのページに戻ると、以下のようにアクセス認証が得られたことが確認できます。

![gemma_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/0504ccd9-5d07-2029-16b3-fc23ec5bfef7.png)

## アクセストークンの作成

モデルをダウンロードする際のユーザー認証のために**Hugging Face**のアクセストークンを作成します。

**Hugging Face**で[Settings](https://huggingface.co/settings/profile)に移動し、さらに[Access Tokens](https://huggingface.co/settings/tokens)に移動します。

ここで**New Token**を押します。

![gemma_4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/b2a1ba17-5c2a-1302-162e-2b6c24a3ae8b.png)

**Name**はわかりやすく**gemma**としました。**Role**は読み取りだけなので**read**にしました。

トークン情報をハードコーディングするのはセキュリティ的によろしくないので、自環境の環境変数にセットしました。

まず設定ファイルを開きます。私の環境では**zshrc**です。

```shell-session
$ open ~/.zshrc
```

生成したアクセストークンの値を記録します。
私は**HUGGING\_FACE\_TOKEN**という名前にしました。

```shell-session
export HUGGING_FACE_TOKEN=ここにアクセストークンの値を書く
```

## PYTORCH\_ENABLE\_MPS\_FALLBACKの指定

今回のコードをそのまま実行すると、以下のエラーが出ます。

> NotImplementedError: The operator 'aten::isin.Tensor\_Tensor\_out' is not currently implemented for the MPS device. If you want this op to be added in priority during the prototype phase of this feature, please comment on https://github.com/pytorch/pytorch/issues/77764. As a temporary fix, you can set the environment variable `PYTORCH_ENABLE_MPS_FALLBACK=1` to use the CPU as a fallback for this op. WARNING: this will be slower than running natively on MPS. 

要は「**[MPS(Metal Performance Shaders)](https://developer.apple.com/documentation/metalperformanceshaders)だと行えない処理があるが、その際はCPUを使うという回避策があります。その代わりに遅くなりますが**」というものです。

指示通り、環境変数として以下の指定をしておきます。

```shell-session
export PYTORCH_ENABLE_MPS_FALLBACK=1
```

コードの先頭に以下を書くことでも回避できるのですが、[Pythonのドキュメント](https://docs.python.org/3/library/os.html#os.putenv)に「**On some platforms, including FreeBSD and macOS, setting environ may cause memory leaks.**」という記載があるので避けておきました。

```python
import os
os.environ["PYTORCH_ENABLE_MPS_FALLBACK"] = "1"
```

## サンプルコードの実行

[サンプルコード](https://huggingface.co/google/gemma-2-27b-it#running-the-model-on-a-single--multi-gpu)を動かしてみます。

サンプルコードから以下4点を変更しています。

1. アクセストークンの取得と指定する。
2. **MacBook**の**GPU**を使用するために**device**として**mps**を指定している。
3. そのまま実行すると以下のwarningが出るため、**max\_new\_tokens**を指定する。今回は50としました。これらの値について詳しくは[ドキュメント](https://huggingface.co/docs/transformers/en/main_classes/text_generation#transformers.GenerationConfig)をご覧ください。
	- UserWarning: Using the model-agnostic default `max_length` (=20) to control the generation length. We recommend setting `max_new_tokens` to control the maximum length of the generation. 

コードは以下です。公式サンプルコードからの変更箇所はコメントに記載済みです。

```python
from transformers import AutoTokenizer, AutoModelForCausalLM
import torch
import os  # 追加

hf_token = os.getenv("HUGGING_FACE_TOKEN")  # 追加
tokenizer = AutoTokenizer.from_pretrained(
    "google/gemma-2-27b-it",
    token=hf_token  # 追加
)
model = AutoModelForCausalLM.from_pretrained(
    "google/gemma-2-27b-it",
    device_map="auto",
    torch_dtype=torch.bfloat16,
    token=hf_token  # 追加
)

input_text = "Write me a poem about Machine Learning."
input_ids = tokenizer(input_text, return_tensors="pt").to(torch.device("mps"))  # toのパラメータを変更

outputs = model.generate(**input_ids, max_new_tokens=50)  # max_new_tokensを追加
print(tokenizer.decode(outputs[0]))
```

モデルに対して「**Write me a poem about Machine Learning.**」という文字列を渡しています。
訳すと「**機械学習についての詩を書いてください**」ですね。

前述の通り、初回はモデルのダウンロードが必要となります。

結果は以下でした。出力長が**max\_new\_tokens**の値に従うため、途中で切れていますね。

> In realms of data, vast and deep,
> Where patterns hide and secrets sleep,
> Machine Learning, a beacon bright,
> Unveils the truth with digital light.
> 
> Algorithms, like curious minds,
> Explore the data, seeking finds

日本語に翻訳すると以下です。

> データの領域、広く深い
> パターンが隠れ、秘密が眠る場所で
> 機械学習は明るい灯台のように
> デジタルの光で真実を明らかにする
> 
> アルゴリズムは好奇心旺盛な心のように
> データを探り、発見を求めて探求する

[前回記事](https://qiita.com/nabata/items/01cf907c289db3fdb47d)同様の感想になりますが、詩的、かな。

## 会話形式の入力プロンプト

[公式の説明](https://huggingface.co/google/gemma-2-27b-it#chat-template)を参考にし、入力文の指定箇所を以下のように変更します。

```python
# input_text = "Write me a poem about Machine Learning."
input_text = """
<start_of_turn>user
Write a hello world program<end_of_turn>
<start_of_turn>model
"""
```

モデルに対して「**Write a hello world program**」という文字列を渡しています。
「**Hello Worldのプログラムを書いてください**」ですね。

本当はサンプル通りに**chat\_template**を用いた方がいいのでしょうが、以下のエラーが出たので**input\_text**にすべて指定しました。

> ValueError: Can't infer missing attention mask on `mps` device. Please provide an `attention_mask` or use a different device.

**chat\_template**の形式については[tokenizer_config.json](https://huggingface.co/google/gemma-2-27b-it/blob/main/tokenizer_config.json#L1747)に書かれているので、よければ合わせてご参照ください。

結果は以下です。

> ```python
> print("Hello, world!")
> ```
> 
> This program will print the text "Hello, world!" to the console.
> 
> **Explanation:**
> 
> * `print()` is a built-in function in Python that displays text on the screen

きちんと**Hello Worldのプログラム**を書いてくれています。
解説もセットです。

## 日本語の入力プロンプト

```python
input_text = """
<start_of_turn>user
日本の都道府県を5つ教えてください<end_of_turn>
<start_of_turn>model
"""
```

「**日本の都道府県を5つ教えてください**」という問いかけをしてみました。

結果は以下です。終端を示す&lt;end\_of\_turn&gt;と&lt;eos&gt;は省いています。

> 日本の都道府県を5つですね！
> 
> 1. **東京都**
> 2. **北海道**
> 3. **大阪府**
> 4. **神奈川県**
> 5. **愛知県** 
> 
> 
> どうでしょうか？

実行回数が少ないので単純に比較できるものでもないでしょうが、[前回記事](https://qiita.com/nabata/items/01cf907c289db3fdb47d)における**7bと2bの回答**と違って正しいです。

## gemma-2-9b-itでの実行

**9b-it**で同じことを行ってみました。ちなみにモデルの容量は**約18.5GB**でした。

コードの変更箇所は以下の通りです。2行ある**gemma-2-27b-it**を**gemma-2-9b-it**に変更しています。

```python
tokenizer = AutoTokenizer.from_pretrained(
    # "google/gemma-2-27b-it",
    "google/gemma-2-9b-it",
    token=hf_token
)
model = AutoModelForCausalLM.from_pretrained(
    # "google/gemma-2-27b-it",
    "google/gemma-2-9b-it",
    device_map="auto",
    torch_dtype=torch.bfloat16,
    token=hf_token
)
```

結果は以下でした。

> 日本の都道府県を5つ紹介しますね！
> 
> 1. **東京都** (東京)
> 2. **大阪府** (大阪)
> 3. **愛知県** (名古屋)
> 4. **北海道** (札幌)
> 5.

また**max\_new\_tokens**の値が小さいので途中で切れてしまいました。()内がなにを意味するかはわかりませんが、4つ目までは正解です。

## 最後に

この環境で動かすにはかなり重い処理ではありますが、手元でこの高品質モデルを動かせるありがたさを強く感じます。

## 宣伝

[SupershipのQiita Organization](https://qiita.com/organizations/supership-inc)を合わせてご覧いただけますと嬉しいです。他のメンバーの記事も多数あります。

Supershipではプロダクト開発やサービス開発に関わる方を絶賛募集しております。
興味がある方は[Supership株式会社 採用サイト](https://supership.jp/recruit/)よりご確認ください。