マルチモーダル言語モデルPhi-3.5-vision-instructをvllmで高速推論するテスト

はじめに

ローカルマシンで動くマルチモーダル言語モデルの性能が上がっています。

本記事では、軽量・ローカルの強みを生かして、動画に対して高速でテキストをアノテーションしてみます。

セットアップ

以下の記事を参考に、vision系のライブラリなどを入れます。

高速推論のためのvllmを入れます。グラフ表示系のライブラリも入れます。

pip install vllm=0.6.2
pip install seaborn
pip install japanize-matplotlib #日本語フォント用


推論

サーバー立ち上げ

vllmのapiサーバーを立ち上げます。
執筆時点では、日本語性能の高そうなqwen2-vlは対応してなさそうだったので、モデルにはphi-3.5を使います。

 vllm serve microsoft/Phi-3.5-vision-instruct --max-model-len 4096  \
--trust-remote-code --limit-mm-per-prompt image=1 --port 8001

マシンはRTX-6000Adaを使用しました。VRAMは46GBほど消費していました。

クライアント

今回は、予め撮影した動画に対して、テキストを生成していきます。
原理的には、USBカメラの画像などに対してリアルタイムにアノテーションしていくことも可能です。

openaiのクライアントモジュールを使います。

関数定義

import cv2
import base64
from openai import OpenAI
import japanize_matplotlib
import matplotlib.pyplot as plt
from IPython.display import clear_output
import time
from matplotlib.gridspec import GridSpec
import os
import numpy as np

# 画像をBase64エンコードする関数
def encode_image_to_base64(image):
    _, buffer = cv2.imencode('.jpg', image)
    encoded_string = base64.b64encode(buffer).decode('utf-8')
    return f"data:image/jpeg;base64,{encoded_string}"


# 長いキャプションを適当な長さで改行
def wrap_text(text, width=40):
    words = text.split(" ")  # スペースで単語に分割
    lines = []  # 改行された行を格納するリスト
    current_line = ""  # 現在の行を一時的に保持

    for word in words:
        # 現在の行に単語を追加しても幅を超えない場合は追加
        if len(current_line) + len(word) + 1 <= width:
            if current_line:  # 既に何か入っている場合、スペースを追加してから単語を追加
                current_line += " " + word
            else:
                current_line = word
        else:
            # 幅を超える場合は現在の行を確定させ、次の行に移る
            lines.append(current_line)
            current_line = word  # 新しい行に最初の単語を設定
    
    # 最後の行をリストに追加
    if current_line:
        lines.append(current_line)
    
    # 改行で結合して返す
    return "\n".join(lines)

# フレームを指定数スキップ
def skip_frames(cap, num_frames):
    for _ in range(num_frames):
        ret, _ = cap.read()  # フレームを読み捨て
        if not ret:
            break

クライアント関連の定義

# OpenAIクライアントの設定
openai_api_key = "EMPTY"
openai_api_base = "http://localhost:8002/v1"
client = OpenAI(api_key=openai_api_key, base_url=openai_api_base)



# 動画ファイルのパス (適当に設定)
video_path = 'mov/sci1.mp4'

動画処理
リアルタイム感を出すため、処理に要した時間分のフレームはスキップしました。


# OpenCVで動画を読み込む
cap = cv2.VideoCapture(video_path)

fps = cap.get(cv2.CAP_PROP_FPS)  # フレームレートを取得
if fps == 0:  # 万が一fpsが取得できない場合の対処
    fps = 30

# 出力ファイルの設定
fourcc = cv2.VideoWriter_fourcc(*'mp4v')  # 出力フォーマット(MP4)
output_path = video_path.replace(".mp4","_cap.mp4")  # 保存する動画ファイルのパス
frame_size = (640, 480)  # 画像サイズ(動画に合わせる)
out = cv2.VideoWriter(output_path, fourcc, fps, frame_size)

# Matplotlibインタラクティブモードを無効にする
plt.ioff()

# フレームごとにキャプションを生成
while cap.isOpened():
    ret, frame = cap.read()
    if not ret:
        break

    # フレームをBase64にエンコード(仮の関数)
    encoded_image = encode_image_to_base64(frame)

    # プロンプト設定
    prompt = "Describe what is in the image."

    # キャプション生成にかかる時間の計測開始
    start_time = time.time()

    # APIにリクエスト(仮想的なクライアント)
    response = client.chat.completions.create(
        model="microsoft/Phi-3.5-vision-instruct",
        messages=[
            {
                "role": "user",
                "content": [
                    {"type": "text", "text": prompt},
                    {"type": "image_url", "image_url": {"url": encoded_image}}
                ]
            }
        ],
        temperature=0,
        max_tokens=100,
        n=1
    )

    # キャプションの取得と改行処理
    caption = response.choices[0].message.content
    wrapped_caption = wrap_text(caption, width=30)  # 30文字ごとに改行

    # 固定サイズの図を作成
    fig = plt.figure(figsize=(10, 6))  # 全体のサイズを設定
    gs = GridSpec(1, 2, width_ratios=[3, 1])  # 画像:キャプションの幅の比率を3:1に設定

    # 画像部分
    ax_image = fig.add_subplot(gs[0])  # GridSpecの左側に画像
    ax_image.imshow(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
    ax_image.axis('off')  # 軸を非表示に

    # キャプション部分
    ax_caption = fig.add_subplot(gs[1])  # GridSpecの右側にキャプション
    ax_caption.text(0.5, 0.5, wrapped_caption, ha='center', va='center', wrap=True, fontsize=12)
    ax_caption.axis('off')  # キャプションエリアの軸も非表示

    # 図を描画し、画像として保存
    fig.canvas.draw()
    image_from_plot = np.frombuffer(fig.canvas.tostring_rgb(), dtype=np.uint8)
    image_from_plot = image_from_plot.reshape(fig.canvas.get_width_height()[::-1] + (3,))

    # Matplotlibの画像をOpenCVフォーマットに変換
    image_from_plot_bgr = cv2.cvtColor(image_from_plot, cv2.COLOR_RGB2BGR)
    resized_image = cv2.resize(image_from_plot_bgr, frame_size)  # 動画サイズに合わせてリサイズ

    # キャプション生成にかかった時間を計測
    elapsed_time = time.time() - start_time

    # フレームスキップ数を計算
    num_frames_to_skip = int(elapsed_time * fps)

    # スキップしたフレーム分だけ同じフレームを繰り返し書き込む
    for _ in range(num_frames_to_skip + 1):  # 元のフレームも含めて繰り返し
        out.write(resized_image)
    
    # 表示をクリア
    plt.clf()

# 終了処理
cap.release()
out.release()
plt.close()
cv2.destroyAllWindows()

生成の様子

自動実験の様子

本日誕生した東京科学大学の様子


まとめ

小型のマルチモーダルモデルは、リアルタイムに近い速度で画像に対するアノテーションが可能なことがわかりました。
今回は4 B程度のモデルで1 fps程度でしたが、2BのQwen2などを使えば、更に高速化できそうです。
定点カメラでの異常検知や見守り、何らかの作業の観察・記録、といった用途に使えるような気がしました。

この記事が気に入ったらサポートをしてみませんか?

ピックアップされています

ローカルLLM関連記事

  • 34本

コメント

コメントを投稿するには、 ログイン または 会員登録 をする必要があります。
化学・大規模言語モデル・ロボットなど 記事はメモ書きですのでご了承ください
マルチモーダル言語モデルPhi-3.5-vision-instructをvllmで高速推論するテスト|Kan Hatakeyama
word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word

mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1