😍

Cloud Run GPU 上の PaliGemma2 に私の娘は可愛いと言わせるまで

2024/12/16に公開

この記事は 3-shake Advent Calendar 2024 シーズン1 16日目の記事 & Jagu'e'r Advent Calendar 2024 4日目の記事 になります。

3-shake に入社してそろそろ丸2年が経過しようとしており、感慨深く思っております。こういうカレンダーをちゃんと埋められているのをみていても、アウトプットという形で自己研鑽や表現を行う素晴らしいメンバーが多いなと日々日々感じております。そんな中で書けるのも良い経験だと感じております。

という前置きを入れつつ、今回は生成 AI の中でも OSS でマルチモーダルな LLM である PaliGemma2 を使ってみたという形で書かせていただきます。正直11月初めは最初は PaliGemma だったんですが、2 がでてしまったのでもうやるしか無いわなと言う感じです。

概要

以前 Gemma2 を Cloud Run で実行することについてまとめておりました。これを PaliGemma2 でできるかどうかでやってみつつ、最近生まれた私の娘が可愛いと言わせることをやります。

前準備

まず使うモデルを選択してきます。前回の Gemma2 と同じように Ollama でやろうと思いましたが、見つからないので Hugging Face から拝借します。

paligemma2 で検索すると結構件数でてきます。
https://huggingface.co/models?sort=trending&search=paligemma2

例えば、 paligemma2-3b-pt-224 の場合 以下のような説明になっており、これから推測すると名前付けとして paligemma2-{モデルの重みの大きさ}-{学習状態}-{画素} のようですね。

Transformers PaliGemma 2 3B weights, pre-trained with 224*224 input images and 128 token input/output text sequences. The model is available the bfloat16 format for fine-tuning.

学習状態は、 pretrained か DOCCI dataset での fine tuned されたものの2パターンあるようです。

今回は動かしてみたという検証で、精度とかの云々カンヌンはおいておきたいので、この中ではそこまで大きくない 3b で、 predtrained されている、 224*224 画素対応のモデルを利用させていただきます。

https://huggingface.co/google/paligemma2-3b-pt-224

896 でもやってみましたが正直変わらんというのと、返却速度がこちらのほうが2倍ぐらい早いので、224 にしています。どうやらメモリ使用量がぜんぜん違うようです。

そして、利用のためには以下のライセンス承認を行っていく必要があります。

とりあえず起動させてみる

動かせるようにするためには、vLLM または transformers ライブラリを利用します。
他には Hugging Face 上から Google Cloud 連携をして 上で動かすという手がありますが、現在 Machine spec を選択することができず deploy までいけません。


なんで。。。うまくいかない。。。他の Gemma 2 とかは動きます。

今回は transformers ライブラリを利用します。なんでかで言えば、色々勉強してみたいという理由だけです。まぁ、Cloud Run で後にやってみた結果動かなかったというのもあるのですが。
ローカルで作業できないとデプロイがいちいち上げ直しで厳しいので、Compute Engine で一度確認してみます(私の Mac でも動きましたが、再起動などすると、メモリが足りなくなって強制終了したりしたので
Compute Engine には Debian 11, Python 3.10. With CUDA 11.8 preinstalled. の OS Image があるのでそれを利用します。このバージョンは、他にもありますのでご利用に合わせて使ってみてください。

こちらの環境にて uv を使って動かします。

uv add pillow requests torch transformers

んで以下のようにコードを書いておきます。

from PIL import Image
import requests
from transformers import AutoProcessor, PaliGemmaForConditionalGeneration
import torch

model = PaliGemmaForConditionalGeneration.from_pretrained("google/paligemma2-3b-pt-896")
processor = AutoProcessor.from_pretrained("google/paligemma2-3b-pt-896")

import sys

prompt= sys.argv[1]
url = sys.argv[2]
image = Image.open(requests.get(url, stream=True).raw)

inputs = processor(images=image, text=prompt, return_tensors="pt")
input_len = inputs["input_ids"].shape[-1]

with torch.inference_mode():
    generation = model.generate(**inputs, max_new_tokens=100, do_sample=False)
    generation = generation[0][input_len:]
    decoded = processor.decode(generation, skip_special_tokens=True)
    print(decoded)

そして、起動前に huggingface-cli login を忘れず実施します。

そして、とりあえずどこから拾ってきた、猫の画像を入れてみます。

簡素ですが返ってきます。

$ uv run hello.py "Is the thing in the image cute? Please answer subjectively." https://user0514.cdnw.net/shared/img/thumb/nekocyanPAKE4524-437_TP_V4.jpg

Loading checkpoint shards: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████| 2/2 [00:01<00:00,  1.19it/s]
You are passing both `text` and `images` to `PaliGemmaProcessor`. The processor expects special image tokens in the text, as many tokens as there are images per each text. It is recommended to add `<image>` tokens in the very beginning of your text. For this call, we will infer how many images each text has and add special tokens.
The 'batch_size' attribute of HybridCache is deprecated and will be removed in v4.49. Use the more precisely named 'self.max_batch_size' attribute instead.

yes

猫が可愛いのは、まぁ知ってるのででは私の娘の画像を渡してみます。

とはいえ、このままだと URL に入れないといけないので、flask でリクエストで受けられるように作り変えます。簡単に Flask を使いますので

uv add flask

で以下のようにファイルを作成していきます。

gemma.py
from transformers import AutoProcessor, PaliGemmaForConditionalGeneration
import torch

model = PaliGemmaForConditionalGeneration.from_pretrained("google/paligemma2-3b-pt-896")
processor = AutoProcessor.from_pretrained("google/paligemma2-3b-pt-896")


def generate(prompt: str, image):
    inputs = processor(images=image, text=prompt, return_tensors="pt")
    input_len = inputs["input_ids"].shape[-1]
    response = ""
    with torch.inference_mode():
        generation = model.generate(**inputs, max_new_tokens=500, do_sample=False)
        generation = generation[0][input_len:]
        response = processor.decode(generation, skip_special_tokens=True)
    return response
app.py
from flask import Flask, request
from gemma import generate
from PIL import Image

app = Flask(__name__)


@app.route("/", methods=["POST"])
def gen():
    image = Image.open(request.files['image'])
    prompt = request.form['prompt']
    return generate(prompt, image)


if __name__ == "__main__":
    app.run(debug=True)

作成したら起動します。

uv run app.py

顔は個人情報に当たるし、可愛いから連れ去られないようにしないといけないから隠させていただきます。本当は見せないとどんぐらいかわi(以下略

ではこれを動かしてみましょう

curl -X POST -F image=@ムッスメの画像 -F prompt="Is the child in this image cute?" http://localhost:5000

yes

わかってそうやな!可愛いのは当たり前だよな!!!

今回見ている限り Gemini などと比べてもプロンプトをチューニングしても詳しく返答してくれるわけではないというのがわかります。

Cloud Run で動かす

vLLM は以下で紹介されており、以下の方法で動かせるようです。
https://codelabs.developers.google.com/codelabs/how-to-run-inference-cloud-run-gpu-vllm?hl=ja#4

ですが、PaliGemma2 でやった場合にはうまくいきませんでした、原因調査は今持って行っています。

そのため、terraformer でのパターンのアプリケーションを起動します。本当は Gunicorn などを挟むのが良いのかもしれませんが、そこまですると私の PC が動かなくなってしまう可能性があったので、簡略にしています。
また host の設定と、Debug の削除、health check に対する endpoint の設定を忘れずに実施してください。

app.py
if __name__ == "__main__":
    app.run(debug=False, host='0.0.0.0')

Dockerfile は以下のようにします。できれば、token は Secret Manager に入れましょう。

FROM python:3.11

WORKDIR /usr/src/app

ENV UV_SYSTEM_PYTHON=1 \
    UV_COMPILE_BYTECODE=1 \
    UV_CACHE_DIR=/root/.cache/uv \
    UV_LINK_MODE=copy

RUN --mount=from=ghcr.io/astral-sh/uv:0.4.28,source=/uv,target=/bin/uv \
    --mount=type=bind,source=uv.lock,target=uv.lock \
    --mount=type=bind,source=pyproject.toml,target=pyproject.toml \
    --mount=type=cache,target=${UV_CACHE_DIR} \
    uv export --frozen --no-dev --format requirements-txt > requirements.txt \
    && uv pip install -r requirements.txt 

ENV HF_TOKEN=${Hugging Face の AccessToken}
RUN huggingface-cli download google/paligemma2-3b-pt-224

COPY app.py .
COPY gemma.py .

EXPOSE 5000

ENTRYPOINT ["python", "app.py"]

前回のGemma2 と同じように
Image の deploy と cloud run の deploy を行います。
全く同じコマンドになるので省略します。PORT だけは違います。

先程と同じ画像を投げてみます。

curl -X POST -F image=@ムッスメの画像 -F prompt="Is the child in this image cute?" -H "Authorization: Bearer $(gcloud auth print-identity-token)" https://xxxxxxxx.us-central1.run.app/gen

yes

わかってそうやな!可愛いのは当たり前だよな!!! (大事なので2回言いました)

応答としては20秒ぐらいかかりますね。メモリ CPU ともに上限で deploy しているのでこれ以上は厳しいです。

まとめ

今回 Cloud Run GPU で PaliGemma2 にて transformer での娘の画像を認識させるところまでをやらせていただきました。
使ってみた限りあまりにこの分野に慣れてないことが見えてきて、もっと勉強が必要だとも感じました。

正直慣れていない分、Compute Engine までで良いんじゃね?みたいに何度か折れそうになりましたが、娘への愛が勝利しました。
そして、私は娘が可愛いということを PaliGemma2 が認めてくれたことが嬉しいと思います。

Discussion