Google Gemma 3 × Cloud Run with GPU：サーバーレスでマルチモーダルLLMを動かす

# はじめに
Google の 最新オープンモデル Gemma 3がリリースされました。

https://blog.google/technology/developers/gemma-3/

特徴として以下が挙げられています。
- **最先端のパフォーマンス**
    - LMArena のリーダーボードでLlama-405B、DeepSeek-V3、o3-miniを上回る
- **多言語対応**
    - 140以上の言語をサポート
- **マルチモーダル**
    - テキスト、画像、動画の入力
- **拡大されたコンテキストウィンドウ**
    - 128Kトークン
- **Function Calling**
- **量子化による高速化**

最新のGemma 3をGoogle Cloud のCloud RunのGPU上で動作させてみたいと思います。
マルチモーダル対応したとのことなので、画像入力も試してみます。


# 方法
## Cloud Run上でGPUを使用できるようにリクエストする
現在プレビュー機能となっているので、割り当てをリクエストしておきます。

https://cloud.google.com/run/docs/configuring/services/gpu?hl=ja

## Dockerイメージの作成
Ollamaで提供されているので、モデル名を指定してDockerイメージを作成します。
```docker
FROM ollama/ollama:latest

# Listen on all interfaces, port 8080
ENV OLLAMA_HOST 0.0.0.0:8080

# Store model weight files in /models
ENV OLLAMA_MODELS /models

# Reduce logging verbosity
ENV OLLAMA_DEBUG false

# Never unload model weights from the GPU
ENV OLLAMA_KEEP_ALIVE -1 

# Store the model weights in the container image
ENV MODEL gemma3:4b
RUN ollama serve & sleep 5 && ollama pull $MODEL 

# Start Ollama
ENTRYPOINT ["ollama", "serve"]
```

Dockerイメージをビルドして、Artifact RegistryにPushします。

https://ollama.com/library/gemma3

## Cloud Runにデプロイ
Artifact RegistryにPushしたイメージを指定して、GPUを使用するようにデプロイします。

## テスト
Cloud Run プロキシを使用して、アクセスできるようにします。

```bash
gcloud run services proxy {サービス名} --port=9090
```

### テキスト

リクエストをしてみます。

```bash
curl http://localhost:9090/api/generate -d '{
  "model": "gemma3:4b",
  "stream": false,
  "prompt":"日本一高い山の名前とその標高は？"
}'
```

回答
```
"response":"日本一高い山は**富士山**です。\n\n*   **標高:** 3,776.24m"
```
標高の小数点以下は少し間違えていますが、概ね合っています！

### テキスト＋画像

以下の画像を入力にしてみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3968617/f00cb96d-73b7-416f-b117-bba03b5faa3e.png)

Pythonのollamaからリクエストをしてみます。

```python
from ollama import Client

client = Client(host='http://localhost:9090')

response1 = client.chat(
    model = "gemma3:4b",
    messages = [{
        "role": "user",
        "content": "画像を説明して",
        "images": ["/path/image.png"],
    }],
)
print(response1["message"]["content"])
```

回答
```markdown
この画像は、雪が降る中、日本の繁華街の交差点で、柴犬と猫が一緒に立っている様子を捉えたものです。

**主なポイント:**

*   **雪景色:** 背景には雪が降っていることがわかります。街並みは雪に覆われ、白銀の世界となっています。
*   **柴犬:** 躍動感のあるポーズで、飛び跳ねている柴犬が写っています。
*   **猫:** 柴犬の横に立っている白い猫は、赤い帽子をかぶっており、可愛らしい印象です。
*   **背景:** 背景には、高層ビルや看板、多くの人々が歩いている様子が写っており、活気のある日本の街並みが広がっています。
*   **交差点:** 柴犬と猫は、白線が描かれた交差点に立っており、周囲には多くの歩行者がいます。

全体的に、この写真は、雪景色の中で、犬と猫が一緒に楽しんでいる様子を捉えた、可愛らしい写真です。
```

画像の内容を詳細に説明してくれました！

次にOCRを試してみます。
以下のダミーの請求書に対して、JSONで出力してくださいと指示してみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3968617/becafce9-3859-48c5-9510-6b0876d01c23.png)

回答
```json
[
  {
    "品名": "りんご",
    "数量": 3,
    "単価": 200,
    "合計": 600
  },
  {
    "品名": "布団",
    "数量": 1,
    "単価": 33000,
    "合計": 33000
  },
  {
    "品名": "Orange",
    "数量": 4,
    "単価": 150,
    "合計": 600
  }
]
```

ちゃんと項目を抽出してくれました！

# おわりに
Googleの Gemma 3を試してみました。
モデルサイズが小さいながらも、他のLLMをも凌駕するパフォーマンスであったり、多言語対応やマルチモーダル対応したことによって、より使用用途が広がったと思います。
Gemmaに限らず、他のオープンモデルも続々と公開されているので、競争がもっと激化しそうな予感がしました。
Function Callingの機能も今度試してみたい。

# 参考
過去にも同様にCloud Run上に別のLLMをデプロイしているので、そちらを参考にしてもらえたらと思います。

https://qiita.com/te_yama/items/dea08c3c4f24f14d6514