Turing 社のマルチモーダル LLM heron-blip-v1 を Google Colaboratory から UI 付きで動かす

## はじめに

先日参加した JAWS DAYS 2024 にて紹介されていた、 Turing さんのマルチモーダル LLM を Google Colab で動かしてみます

https://qiita.com/RyoWakabayashi/items/2c8cbd8a92abc667c7e9#aws-llm-%E9%96%8B%E7%99%BA%E6%94%AF%E6%8F%B4%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0%E9%96%8B%E7%99%BA%E8%80%85%E3%81%8B%E3%82%89%E5%AD%A6%E3%81%B6llm%E9%96%8B%E7%99%BA%E3%81%AE%E6%9C%80%E5%89%8D%E7%B7%9A

マルチモーダルとはテキスト、画像、音声など複数種類のデータを一度に扱えることを意味します

本記事で動かしてみる BLIP は、画像を解釈して質問に返答してくれます

AIに写真を見せて「この写真に写っているコップは何色？」みたいな質問ができるわけです

Turing さんは完全自動運転を目指しており、その中で LLM を使おうとしています

https://www.turing-motors.com/

車に搭載したカメラの映像から周囲のモノを認識し、適切な運転をさせようという試みですね

Turing 棚橋さんの記事はこちら

https://zenn.dev/turing_motors/articles/00df893a5e17b6

前回書いた Stockmark 社の日本語 LLM を動かす記事はこちら

https://qiita.com/RyoWakabayashi/items/027fb61f9ea19282a669

## モデルの取得元

モデルは Hugging Face で公開されています

https://huggingface.co/turing-motors/heron-chat-blip-ja-stablelm-base-7b-v1

モデルのダウンロードに Hugguing Face のアクセストークンが必要になるので、サインアップしてトークンを用意しておきましょう

設定画面の左メニューから「Access Tokens」を開き、「New token」ボタンでトークンが作成できます

https://huggingface.co/settings/tokens

![スクリーンショット 2024-03-22 16.48.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/eb7c556b-9361-92c7-cfeb-163442a68bed.png)

## Google Colab のランタイム

7b = 70億パラメーターのモデルを使います

GPU RAM は 16.2 GB 使われたため、ランタイムには A100 GPU を選択する必要があります

お金はかかりますが、 Google Colab Pro を契約しましょう

https://colab.research.google.com/signup/pricing?hl=ja

他の LLM も色々触ってみるのであれば、結果安上がりだと思います


## ノートブックの設定

新しいノートブックを開きます

上メニューから「ランタイム」 |> 「ランタイムのタイプを変更」を開きます

![スクリーンショット 2024-03-22 17.34.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/d44ea3fd-c019-8f4b-b69b-c225e6629cea.png)

ハードウェアアクセラレータで「A100 GPU」を選択し、「保存」をクリックします

![スクリーンショット 2024-03-22 17.34.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/2dd4cf70-8814-fcd8-1657-c7a9263e9411.png)

左メニューの鍵アイコンをクリックし、 `HF_TOKEN` という名前で Hugging Face のアクセストークンをシークレットに登録しておきます

「ノートブックからのアクセス」を忘れず ON にしておきましょう

![スクリーンショット 2024-03-24 12.36.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/42bcddf5-e821-7470-a849-51bb52f2bf11.png)

## セットアップ

以降、 Colab のセルにコードを貼り付けて実行します

:::note warn
初回セル実行時、場合によっては「A100 GPU」を使うことができず、「V100 GPU」で実行されてしまうことがあります

そのまま進んでも GPU RAM 不足になるため、その場合はランタイムを接続解除、削除しましょう

しばらく待ってから改めてランタイムタイプに「A100 GPU」を選択し、再度実行して A100 が使えるまでリトライします

リトライが嫌な場合は Colab のさらに上のプランを契約しましょう
:::

Heron の GitHub リポジトリーをクローンしてきます

```sh
!git clone https://github.com/turingmotors/heron.git
```

Heron は Turing 社が開発しているマルチモーダル学習ライブラリです

https://github.com/turingmotors/heron

Heron のモジュールを使うため、ディレクトリーを移動します

```sh
%cd heron
```

pip 自体を最新化してから、必要なモジュールをインストールします

```sh
!pip install --upgrade pip
!pip install accelerate datasets deepspeed einops evaluate gradio peft protobuf sentencepiece transformers wandb japanize-matplotlib
```

:::note info
公式の Heron インストール手順に従うのであれば以下のコマンドになります

```sh
!pip install --upgrade pip
!pip install -r requirements.txt
!pip install -e .
```

しかし、上記手順でインストールすると開発用のモジュール（isort や black、pre-commit）等もインストールされてしまいます

また、 Jupyter 自体の更新もされるため、セッション再起動を促されてしまいます

不要なインストールを避けるため、モジュールを個別に指定しています

更に、今回は簡単な UI を構築するため、 Gradio を追加しています
:::

モジュールをインポートします

```python
import gradio as gr
import requests
import torch
from heron.models.video_blip import VideoBlipForConditionalGeneration, VideoBlipProcessor
from PIL import Image
from transformers import LlamaTokenizer
```

:::note warn
`gradio` は最後に UI を作るためのものなので、モデルを動かしたいだけの場合は不要です
:::

## モデルのロード

Hugging Face からモデルをダウンロードし、ロードします

```python
device_id = 0
device = f"cuda:{device_id}"

MODEL_NAME = "turing-motors/heron-chat-blip-ja-stablelm-base-7b-v1"
model = VideoBlipForConditionalGeneration.from_pretrained(MODEL_NAME)
```

:::note warn
公式の手順では以下のようにロードしています

```python
model = VideoBlipForConditionalGeneration.from_pretrained(
    MODEL_NAME, torch_dtype=torch.float16, ignore_mismatched_sizes=True
)
```

残念ながらこの手順だとエラーが発生したため、オプションの指定を削除しています

ここで `torch_dtype` を指定しなくても、この後のコードで半精度に変換しているので問題なさそうです
:::

モデルを半精度に変換してから、使用するデバイスを GPU に変更します

```python
model = model.half()
model.eval()
model.to(device)
```

前処理を準備します

```python
processor = VideoBlipProcessor.from_pretrained("Salesforce/blip2-opt-2.7b")
tokenizer = LlamaTokenizer.from_pretrained("novelai/nerdstash-tokenizer-v1", additional_special_tokens=['▁▁'])
processor.tokenizer = tokenizer
```

## 生成

画像とテキストを入力として、それに応じたテキストを生成します

まずは、Hugging Face の Usage と同じ入力を使います

```python
url = "https://www.barnorama.com/wp-content/uploads/2016/12/03-Confusing-Pictures.jpg"
image = Image.open(requests.get(url, stream=True).raw)

image
```

![test](https://www.barnorama.com/wp-content/uploads/2016/12/03-Confusing-Pictures.jpg)

```python
text = f"##human: この画像の面白い点は何ですか?\n##gpt: "
```

画像とテキストに前処理を施します

```python
inputs = processor(
    text=text,
    images=image,
    return_tensors="pt",
    truncation=True,
)

inputs = {k: v.to(device) for k, v in inputs.items()}
inputs["pixel_values"] = inputs["pixel_values"].to(device, torch.float16)

eos_token_id_list = [
    processor.tokenizer.pad_token_id,
    processor.tokenizer.eos_token_id,
    int(tokenizer.convert_tokens_to_ids("##"))
]
```

生成を実行します

```python
with torch.no_grad():
    out = model.generate(**inputs,
                         max_length=256,
                         do_sample=False,
                         temperature=0.,
                         eos_token_id=eos_token_id_list,
                         no_repeat_ngram_size=2)

# print result
print(processor.tokenizer.batch_decode(out)[0])
```

結果は以下のようなテキストになります

> 画像の面白い点は、黄色いタクシーの荷台に立っている男性が、タクシーに積まれたアイロン台でアイロンの仕事をしていることだ。このシーンは、アイロンは通常アイロニングやプレスに使われるものであり、日常的なアイテムの典型的な使い方ではないため、興味深く、珍しい。アイロールをしている男性は、この型破りなアイデアを実行する技術と専門知識を披露している。



## UI付きで実行

Gradio を使って簡易的な UI を用意します

https://www.gradio.app/

まず、入力を出力に変換するための関数を用意します

```python
def infer(image, text):
    inputs = processor(
        text=text,
        images=image,
        return_tensors="pt",
        truncation=True,
    )

    inputs = {k: v.to(device) for k, v in inputs.items()}
    inputs["pixel_values"] = inputs["pixel_values"].to(device, torch.float16)

    eos_token_id_list = [
        processor.tokenizer.pad_token_id,
        processor.tokenizer.eos_token_id,
        int(tokenizer.convert_tokens_to_ids("##"))
    ]

    with torch.no_grad():
        out = model.generate(**inputs,
                             max_length=256,
                             do_sample=False,
                             temperature=0.,
                             eos_token_id=eos_token_id_list,
                             no_repeat_ngram_size=2)

    return processor.tokenizer.batch_decode(out)[0]
```

Gradio で入出力を定義します

```python
gr.Interface(
    fn=infer,
    inputs=[
        gr.Image(label="Image"),
        gr.Textbox(label="Prompt"),
    ],
    outputs=[gr.Textbox()]
).launch(debug=True)
```

実行すると、以下のような UI が表示されます

![スクリーンショット 2024-03-27 11.30.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/fb14cfe3-4323-bf17-bcf1-1c08c505d086.png)

左側の imgae で適当な画像を選択し、 Prompt に適当な文言を入れてみましょう

「Submit」を押すと、右側の output に生成結果が表示されます

:::note info
改行しようとして Enter キーを押すと「Submit」を押したときと同じように生成処理が実行されてしまいます

改行したい場合は Shift キーを押しながら Enter キーを押しましょう
:::

![スクリーンショット 2024-03-27 11.56.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/cc3a60d2-6dfa-0011-64e6-929affb55739.png)

![スクリーンショット 2024-03-27 11.44.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/0bd1c09f-e4ae-0d50-6ecb-130070e9c5cc.png)

![スクリーンショット 2024-03-27 11.40.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/3c246cd8-8e08-5157-e954-c74fa41ac7a6.png)

![スクリーンショット 2024-03-27 11.41.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/7a7dc3b1-f651-fde8-738d-0443a40c7538.png)

一通り遊び終わったら上メニューから「ランタイム」|>「ランタイムを接続解除して削除」を選択し、ランタイムを削除しておきましょう

ランタイムを動かし続けていると、無駄に コンピューティング・ユニットを消費してしまいます

## まとめ

Turing 社のマルチモーダル LLM heron-blip-v1 を Google Colaboratory で動かすことができました

複雑なものは説明が難しいようですが、用途を限定すれば実用できるかもしれません