👀

ローカル環境でQwen2-VLを動かしてみる

2024/09/01に公開

はじめに

どんな人向けの記事?

  • 生成 AI、特に VLM(Vision Language Model)に興味がある方
  • Qwen2-VL に興味がある方
  • Qwen2-VL をローカル環境で動かしたい方(Windows, Ubuntu, Mac)
環境
1. Mac Studio(M2 Ultra 128GB)
2. Windows 10 (メインメモリ 96GB + RTX 3060 12GB)

★2024-11-18(月)追記

本記事ではサンプルプログラムを動かすことにフォーカスして記載していたので、実用上は使いづらいと思います。

Web UIAPI として使いたい場合は、本記事の方法ではなく以下をおすすめします。

  • 公式リポジトリの README に従って web_demo_mm.py を使う方法

https://github.com/QwenLM/Qwen2-VL

  • vllm サーバーを立ち上げて Open WebUI などで利用する方法(NVIDIA 環境のみ)

https://note.com/hiroking100/n/nfc5ef3744b7f


内容

今回の記事では、Alibaba Cloud が発表した最新の VLM であるQwen2-VLを、ローカル環境で実際に使ってみようと思います。

本記事では、主に下記についてまとめます。

  • ローカル環境で Qwen2-VL を動かす方法
    • Windows, Ubuntu, Mac
    • Mac 環境で動かすときの注意点
  • Qwen2-VL の使用例(画像+テキスト)
  • 推論速度(実測)

ただし、2B モデルは VRAM が 12GB 以上無いと動かない、もしくは動いてもかなり遅い可能性があります(8GB でもギリギリ動くかも)。7B モデルは 24GB あっても足りないかも知れません。

導入手順

この記事では Qwen2-VL-2B-Instruct をローカル環境で動かす手順を書き留めておきます。7B の場合もモデルが違うだけで手順は全く同じです。基本的な手順としては Hugging face のページに書かれてますが、下記では端折られている部分も記載しています。

https://huggingface.co/Qwen/Qwen2-VL-2B-Instruct

できるだけ Windows,Ubuntu,Mac(Apple Silicon)環境に対応した書き方にしますが、CUDA まわりは各自の環境に合わせ必要に応じて修正してください。

モデル、必要モジュールのダウンロード

  • モデルのダウンロード〜python 仮想環境作成
全OS共通
$ git lfs install
$ git clone https://huggingface.co/Qwen/Qwen2-VL-2B-Instruct
$ cd Qwen2-VL-2B-Instruct
$ python3 -m venv venv

※git lfs install が使えない場合は、git-lfs をあらかじめインストールしておいてください。

  • 仮想環境の activate
Mac/Ubuntu
$ source venv/bin/activate
Windows
$ .\venv\Scripts\activate
  • 必要モジュールのインストール
Windows/Ubuntu
$ pip install git+https://github.com/huggingface/transformers
$ pip install qwen-vl-utils
$ pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu124
$ pip install accelerate>=0.26.0
Mac
$ pip install git+https://github.com/huggingface/transformers
$ pip install qwen-vl-utils
$ pip install torch torchvision
$ pip install accelerate

実行 python スクリプト

ディレクトリ Qwen2-VL-2B-Instruct の中に実行用スクリプトmain.pyを作成します。基本は Hugging face をコピペしてますが、モデルをよくわからない場所に勝手にダウンロードされたくなかったので一部修正してます。

また、下記のスクリプトはQwen2-VL-2B-Instruct/input/test_01.pngという入力画像を読み込み、プロンプトはこの画像を詳しく説明してください。としたものです。画像は各自で用意し、Qwen2-VL-2B-Instruct/input/に格納してください。それとmax token 数1024としていますが、このあたりもお好みで変更してください。

Windows/Ubuntu(CUDA 環境)用の実行用 python スクリプト
main.py
from transformers import Qwen2VLForConditionalGeneration, AutoTokenizer, AutoProcessor
from qwen_vl_utils import process_vision_info

# default: Load the model on the available device(s)
model = Qwen2VLForConditionalGeneration.from_pretrained(
    "../Qwen2-VL-2B-Instruct", torch_dtype="auto", device_map="auto"
)

# We recommend enabling flash_attention_2 for better acceleration and memory saving, especially in multi-image and video scenarios.
# model = Qwen2VLForConditionalGeneration.from_pretrained(
#     "Qwen/Qwen2-VL-2B-Instruct",
#     torch_dtype=torch.bfloat16,
#     attn_implementation="flash_attention_2",
#     device_map="auto",
# )

# default processer
processor = AutoProcessor.from_pretrained("../Qwen2-VL-2B-Instruct")

# The default range for the number of visual tokens per image in the model is 4-16384. You can set min_pixels and max_pixels according to your needs, such as a token count range of 256-1280, to balance speed and memory usage.
# min_pixels = 256*28*28
# max_pixels = 1280*28*28
# processor = AutoProcessor.from_pretrained("Qwen/Qwen2-VL-2B-Instruct", min_pixels=min_pixels, max_pixels=max_pixels)

messages = [
    {
        "role": "user",
        "content": [
            {
                "type": "image",
                "image": "input/test_01.png",
            },
            {"type": "text", "text": "この画像を詳しく説明してください。"},
        ],
    }
]

# Preparation for inference
text = processor.apply_chat_template(
    messages, tokenize=False, add_generation_prompt=True
)
image_inputs, video_inputs = process_vision_info(messages)
inputs = processor(
    text=[text],
    images=image_inputs,
    videos=video_inputs,
    padding=True,
    return_tensors="pt",
)
inputs = inputs.to("cuda")

# Inference: Generation of the output
generated_ids = model.generate(**inputs, max_new_tokens=1024)
generated_ids_trimmed = [
    out_ids[len(in_ids) :] for in_ids, out_ids in zip(inputs.input_ids, generated_ids)
]
output_text = processor.batch_decode(
    generated_ids_trimmed, skip_special_tokens=True, clean_up_tokenization_spaces=False
)
print(output_text)
  • Mac(Apple Silicon 環境)用
    Mac 用のスクリプトは上記をコピペして、下記の 1 行を修正するだけです。この変更により Qwen2-VL が Apple Silicon で動くようになります。
main.py
-   inputs = inputs.to("cuda")
+   inputs = inputs.to("mps")

以上で、Qwen2-VL-2B-Instruct を実行する準備が整ったはずです。

実行例

プロンプトはすべてこの画像を詳しく説明してください。として、3 つの画像を入力したときの、2B、7B、72B モデルの回答を記載します。ただし、2B、7B モデルは RTX 3060 の出力のみ記載しています。

① 画像の説明

  • 入力画像:掛川花鳥園で撮った写真
2B の出力

この画像は、鳥ジャンキーズの漫画の展示板です。展示板には 12 枚の漫画が掲載されています。各ページは異なるキャラクターとシーンを描いており、それぞれのページごとに異なるスタイルや色調で表現されています。

左上隅に「鳥ジャンキーズ」のロゴが表示されており、右上隅には「鳥ジャンキーズ第 2 巻」のタイトルが表示されています。下部には「好評販売中!」という情報が表示さ れています。

各ページのタイトルやキャラクター名は不明ですが、全体的に鳥や人間との関係性が描かれています。特に右下隅のページでは、鳥と人間が一緒にいるシーンが描かれています。

7B の出力

この画像は、日本の漫画やアニメのポスターやチラシが展示されている場所を示しています。ポスターやチラシは、さまざまなキャラクターとシーンが描かれています。

上部には、黄色い背景に黒い文字で書かれた看板があります。看板には、「鳥ジャンキーズ」のタイトルと、その作品が収録された「鳥ジャンキーズ」第 2 巻についての情報を記載されています。

下部には、12 枚のポスタ ーが並んでいます。これらのポスターは、異なるキャラクターとシーンを描いており、一部には鳥や動物のイラストも含まれています。ポスターのデザインは、モノクロとカラーのものがあり、それぞれ独特のスタイルを持っています。

全体的に、この場所はアニメや漫画のファンが楽しむことができる場所であることを示しています。

72B の出力

この画像は、漫画の展示パネルを示しています。パネルの上部には、黄色い背景の看板があり、日本語のテキストが書かれています。テキストは、「鳥ジャンキーズ」の第 2 巻が収録された「鳥川花鳥園編」が、おみやげ広場「ハミングバード」で好評販売中であることを告知しています。

パネルには、複数の漫画のページが展示されています。各ページには、キャラクターの会話やシーンが描かれており、一部のページにはカラーイラストも含まれています。漫画のスタイルは、モノクロとカラーの両方が混在しています。

展示されている漫画の内容は、鳥や動物をテーマにしたもので、キャラクターたちの会話や行動が描かれています。パネルの右上には、紫色の髪のキャラクターが鳥を抱きしめるシーンがカラーで描かれています。

全体的に、このパネルは「鳥ジャンキーズ」の漫画を宣伝するためのもので、読者に作品の魅力を伝えるために様々なシーンが展示されています。

② ワークフローの読み込み

  • 入力画像:LLM 人狼のフロー
2B の出力

この画像は、ゲームの初期設定から NPC の初期化までのプロセスを示しています。以下に詳細を説明します。

  1. ゲームの初期設定:ゲームの基本的な設定が行われます。
  2. NPC の初期化:NPC(非人形キャラクター)の初期設定が行われます。
  3. 日付の進行:ゲームの進行が進行します。
  4. 会話フェーズ:NPC とユーザーの会話が行われます。
  5. NPC が議論:NPC が議論を行うことがあります。
  6. 議論を全 NPC が記憶:議論が全 NPC に記憶されます。
  7. 投票フェーズ:NPC が投票を行 うことがあります。
  8. NPC が投票対象を選定:NPC が投票対象を選定します。
  9. 投票結果を記録:投票結果が記録されます。
  10. 夜のアクションフェーズ:NPC が夜のアクションを行うことがあります。
  11. 占い師、狩人、人狼が対象を選定 :占い師、狩人、人狼が対象を選定します。
  12. アクション結果を次の日に反映:アクション結果が次の日に反映されます。
  13. 終了条件のチェック:終了条件が満たされたら、ゲーム終了と結果のログ保存が行われます。

このプロセスは、ゲームの基本的な流れを示しており、各ステップで必要な処理が行われています。

7B の出力

この画像は、ゲームのフローチャートを示しています。以下に各ステップを詳細に説明します。

  1. ゲームの初期設定:

    • ゲームの開始時に必要なすべての設定が行われます。
  2. NPC の初期化:

    • NPC(非プレイヤーキャラクター)が初期化されます。
  3. 日付の進行:

    • 日付が進み、ゲームの進行が進む。
  4. 会話フェーズ:

    • NPC とプレイヤーと の会話が始まります。
  5. NPC が議論 ★LLM:

    • NPC が議論し、その結果を記録します。
  6. 議論を全 NPC が記憶:

    • 全 NPC が議論の結果を記憶します。
  7. 投票フェーズ:

    • プレイヤーが投票を行うフェーズです。
  8. NPC が投票 対象を選定 ★LLM:

    • NPC が投票対象を選択し、その結果を記録します。
  9. 投票結果を記録:

    • 投票結果が記録されます。
  10. 夜のアクションフェーズ:

    • 夜のアクションフェーズが始まります。
  11. 占い師、狩人、人狼が対象 を選定 ★LLM:

    • 占い師、狩人、人狼が対象を選択します。
  12. アクション結果を次の日に反映:

    • アクション結果が次の日に反映されます。
  13. 終了条件のチェック:

    • 終了条件がチェックされ、条件が満たされるかどうかを判断 します。
  14. 条件満たす:

    • 条件が満たされた場合、ゲームが終了し、結果がログに保存されます。
  15. ゲーム 終了と結果のログ保存:

    • ゲームが終了し、結果がログに保存されます。

このフローチャートは、ゲームの進行と各フェ ーズの流れを示しています。

72B の出力

この画像は、ゲームのフローチャートを示しています。以下に各ステップを詳細に説明します。

  1. ゲームの初期設定: ゲームが始まる前に必要な設定を行う。
  2. NPC の初期化: NPC(Non-Player Character)の初期設定を行う。
  3. 日付の進行: ゲーム内の日付が進む。
  4. 会話フェーズ: NPC が議論を行うフェーズ。
  5. NPC が議論: NPC が議論を行う。
  6. 議論を全 NPC が記憶: 全ての NPC が議論を記憶する。
  7. 投票フェーズ: NPC が投票を行うフェーズ。
  8. NPC が投票対象を選定: NPC が投票対象を選定する。
  9. 投票結果を記録: 投票結果を記録する。
  10. 夜のアクションフェーズ: 夜のアクションを行うフェーズ。
  11. 占い師、狩人、人狼が対象を選定: 占い師、狩人、人狼が対象を選定する。
  12. アクション結果を次の日に反映: アクションの結果が次の日に反映される。
  13. 終了条件のチェック: ゲームの終了条件をチェックする。
  • 条件満たさない: 条件が満たされない場合は、日付が進行し、再度会話フェーズから始まる。
  • 条件満たす: 条件が満たされた場合は、ゲームが終了し、結果のログが保存される。

このフローチャートは、ゲームの進行と各フェーズの詳細を示しており、NPC の行動や投票、アクションの結果がどのようにゲームに影響を与えるかを示しています。

③ 数式を含む文書の OCR

  • 入力画像
2B の出力

申し訳ありませんが、あなたの質問に回答するための TeX コードは提供できません。しかし、あなたが示した SDE(確率微分方程式)の解く過程を TeX 形式で示すために必要なテキストは以下にあります。

\documentclass{article}
\usepackage{amsmath}

\begin{document}

もちろんです、幾何プラウン運動の確率微分方程式を解く過程をもう一度描いてください。ただし、 数式はTeX形式でお願いします。

1. 几何プラウン運動の確率微分方程式(SDE)は以下の通りです:

$$ dS_t = \mu S_t dt + \sigma S_t dB_t $$


2. 伊藤の公式(または伊藤のレンマ)を思い出します。関数 $f(t, X_t)$ に対して、伊藤の公式は次の形を持っています:

$$ df(t, X_t) = \left( f_t(t, X_t) + \frac{1}{2} f_{xx}(t, X_t)(\sigma^2) \right) dt + f_x(t, X_t) dX_t $$


ここで、$f_t$$t$ に関する偏微分、$f_x$$x$ に関する偏微分、$f_{xx}$$x$ に対する二次の偏微分を表します。

3. 我々が解を求めたいのは $S_t$ です。したがって、$f(t, X_t) = \ln(X_t)$ とおきます。そして、$f_x$, $f_{xx}$, $f_t$ を計算します:

$$ f_x = \frac{1}{X_t}, \quad f_{xx} = -\frac{1}{X_t^2}, \quad f_t = 0 $$


4. 上記の偏微分を伊藤の公式に代入します:

$$ d(\ln(X_t)) = \left( 0 + \frac{1}{2} \left( -\frac{1}{X_t^2} \right) (\sigma^2) \right) dt + \left( \frac{1}{X_t} \right) dX_t $$


$$ d(\ln(S_t)) = -\frac{1}{2} \sigma^2 dt + \frac{1}{S_t} dS_t $$


5. $dS_t$ の式を代入します:

$$ d(\ln(S_t)) = -\frac{1}{2} \sigma^2 dt + \frac{1}{S_t} ( \mu S_t dt + \sigma S_t dB_t ) $$


$$ d(\ln(S_t)) = \left( \mu - \frac{1}{2} \sigma^2 \right) dt + \sigma dB_t $$


6. これは通常の微分方程式に等しいため、積分します:

$$ \int d(\ln(S_t)) = \int \left( \mu - \frac{1}{2} \sigma^2 \right) dt + \int \sigma dB_t $$


$$ \ln(S_t) - \ln(S_0) = \left( \mu - \frac{1}{2} \sigma^2 \right) t + \sigma B_t $$


$$ \ln \left( \frac{S_t}{S_0} \right) = \left( \mu - \frac{1}{2} \sigma^2 \right) t + \sigma B_t $$


7. 指数関数を適用して、$S_t$ を解きます:

$$ S_t = S_0 \exp \left( \left( \mu - \frac{1}{2} \sigma^2 \right) t + \sigma B_t \right) $$


以上がTeX形式で示した幾何プラウン運動 の確率微分方程式の解く過程です。
\end{document}

このテキストは TeX 形式で表示され、TeX コードを追加することで、TeX 形式で解く過程を説明することができます。

7B の出力

この画像は、幾何ブラウン運動の確率微分方程式を解く過程を TeX 形式で示しています。以下に各ステップを詳細に説明します。

  1. 幾何ブラウン運動の確率微分方程式 (SDE)
    • 幾何ブラウン運動の確率微分方程式は以下の通りです:
dS_t = \mu S_t dt + \sigma S_t dB_t
  1. 伊藤の公式(または伊藤のレマ)
    • 関数 f(t, X_t) に 対して、伊藤の公式は次の形を持っています:
df(t, X_t) = \left( f_t(t, X_t) + \frac{1}{2} f_{xx}(t, X_t) (\sigma^2) \right) dt + f_x(t, X_t) dX_t
  • ここで、f_tt に関する偏微分、f_xx に関する偏微分、f_{xx}x に対する二次の偏微分を表します。
  1. 関数 f(t, X_t) = \ln(X_t)
    • まず、関数 f(t, X_t) = \ln(X_t) を考えます。
    • そして、f_x, f_{xx}, f_t を計算します:
f_x = \frac{1}{X_t}, \quad f_{xx} = -\frac{1}{X_t^2}, \quad f_t = 0
  1. 上記の偏微分を伊藤の公式に代入する
    • 伊藤の公式に代入すると:
d(\ln(X_t)) = \left( 0 + \frac{1}{2} \left( -\frac{1}{X_t^2} \right) (\sigma^2) \right) dt + \left( \frac{1}{X_t} \right) dX_t
d(\ln(S_t)) = -\frac{1}{2} \sigma^2 dt + \frac{1}{S_t} dS_t
  1. dS_t の式を 代入する
    • dS_t の式を代入すると:
d(\ln(S_t)) = -\frac{1}{2} \sigma^2 dt + \frac{1}{S_t} \left( \mu S_t dt + \sigma S_t dB_t \right)
d(\ln(S_t)) = \left( \mu - \frac{1}{2} \sigma^2 \right) dt + \sigma dB_t
  1. これは通常の微分方程式に等しいため、積分します
    • 積分すると:
\int d(\ln(S_t)) = \int \left( \mu - \frac{1}{2} \sigma^2 \right) dt + \int \sigma dB_t
\ln(S_t) - \ln(S_0) = \left( \mu - \frac{1}{2} \sigma^2 \right) t + \sigma B_t
\ln \left( \frac{S_t}{S_0} \right) = \left( \mu - \frac{1}{2} \sigma^2 \right) t + \sigma B_t
  1. 指数関数を適用して、S_t を解きます
    • 指数関数を適用すると:
S_t = S_0 \exp \left( \left( \mu - \frac{1}{2} \sigma^2 \right) t + \sigma B_t \right)

以上の手順で、幾何ブラウ ン運動の確率微分方程式を解くことが可能となります。

72B の出力

この画像は、幾何ブラウン運動の確率微分方程式(SDE)を解く過程をテキストと数式で説明しています。以下に詳細な説明を提供します。

  1. 幾何ブラウン運動の確率微分方程式(SDE):
dS_t = \mu S_t dt + \sigma S_t dB_t

ここで、S_t は時間 t での価格、\mu は期待収益率、\sigma はボラティリティ、dB_t はブラウン運動の増分を表します。 2. 伊藤の公式(または伊藤のレッマ):
伊藤の公式は、関数 f(t, X_t) に対して次の形を持っています:

df(t, X_t) = \left( f_t(t, X_t) + \frac{1}{2} f_{xx}(t, X_t) (\sigma^2) \right) dt + f_x(t, X_t) dX_t

ここで、f_tt に関する偏微分、f_xx に関する偏微分、f_{xx}x に関する二階の偏微分を表します。 3. 関数 f(t, X_t) の選択:
我々が解を求めたいのは S_t です。したがって、f(t, X_t) = \ln(X_t) とおきます。
そして、f_x, f_{xx}, f_t を計算します:

f_x = \frac{1}{X_t}, \quad f_{xx} = -\frac{1}{X_t^2}, \quad f_t = 0
  1. 伊藤の公式への代入:
    上記の偏微分を伊藤の公式に代入します:
d(\ln(X_t)) = \left( 0 + \frac{1}{2} \left( -\frac{1}{X_t^2} \right) (\sigma^2) \right) dt + \left( \frac{1}{X_t} \right) dX_t
d(\ln(S_t)) = -\frac{1}{2} \sigma^2 dt + \frac{1}{S_t} dS_t
  1. dS_t の式の代入:
    dS_t の式を代入します:
d(\ln(S_t)) = -\frac{1}{2} \sigma^2 dt + \frac{1}{S_t} (\mu S_t dt + \sigma S_t dB_t)
d(\ln(S_t)) = \left( \mu - \frac{1}{2} \sigma^2 \right) dt + \sigma dB_t
  1. 積分:
    これは通常の微分方程式に等しいため、積分します:
\int d(\ln(S_t)) = \int \left( \mu - \frac{1}{2} \sigma^2 \right) dt + \int \sigma dB_t
\ln(S_t) - \ln(S_0) = \left( \mu - \frac{1}{2} \sigma^2 \right) t + \sigma B_t
\ln \left( \frac{S_t}{S_0} \right) = \left( \mu - \frac{1}{2} \sigma^2 \right) t + \sigma B_t
  1. 指数関数の適用:
    指数関数を適用して、S_t を解きます:
S_t = S_0 \exp \left( \left( \mu - \frac{1}{2} \sigma^2 \right) t + \sigma B_t \right)

以上が、幾何ブラウン運動の確率微分方程式を解く過程です。

\[\\[[になっていたり、\\\になっていたりしたので、一括置換等の必要はありましたが、それをするだけで TeX でコンパイルできました。また上記では、Zenn の仕様に合わせて数式として表示させるために、\[\]$$(前後に改行を入れる)に\( \)$に置換しています。

推論時間

RTX 3060 と M2 Ultra での、推論時間を比較します。ただし、一つ重大問題がありまして、端末やパラメータサイズが変わると出力が変わるため、フェアな評価ができないということです。

  • その他注意点
    • プログラムの実行から終了までの時間を測定しており、モデルのロード時間含みます。
    • RTX 3060(12GB)で 7B モデルが 2B モデルに比べてやたら遅いのは、おそらく VRAM 不足が原因です。
入力 サイズ RTX 3060 (12GB) M2 Ultra (128GB) 備考
2B 11 秒 174 秒 RTX3060:274 文字、M2:1407 文字
7B 209 秒 23 秒 RTX3060:341 文字、M2:221 文字
2B 18 秒 63 秒 RTX3060:596 文字、M2:759 文字
7B 534 秒 70 秒 RTX3060:846 文字、M2:760 文字
2B 34 秒 167 秒 RTX3060:1877 文字、M2:1713 文字
7B 1074 秒 179 秒 RTX3060:1999 文字、M2:1805 文字

表からわかることとしては下記のとおりでしょうか。

  • M2 より RTX 3060 の方が推論速度が数倍速い(どちらも VRAM に余裕がある場合)。
  • VRAM が足りなくなるとシステムメモリを使って推論するためか、推論時間が激増する(数十倍)。
    • このとき、VRAM が十分にある M2 の方が数倍速い。
  • M2 で VRAM が十分あれば、7B と 2B の推論速度に大きな差はない。

まとめ

今回はローカル環境で Qwen2-VL を動かしてみました。
結論としては下記の通りです。

  • 2B:M2 よりも RTX3060 の方が数倍速い
  • 7B:RTX3060 より M2 の方が数倍速い(VRAM 12GB では足りないため)
  • 2B でも日本語や数式の OCR 性能がかなり高い

数式の OCR 性能がめちゃくちゃ高く、ほぼそのまま TeX 形式として取り込めたのが個人的には驚きでした。手書きのノートや、社内の技術文書を TeX 形式で取り込みたい場合に時短できそうです。他には、自分が作ったプレゼン資料をブラッシュアップするためにも使えそうですね。

また、今回の記事では取り上げませんでしたが、Hugging face のページを見ると動画を入力して推論することもできるそうです。こちらも少し試したのですが、エラーで実行できませんでした。72B の方でもはエラーはでなかったものの、動画として認識しているのか微妙な回答でした。動画については続報あれば、X または別の記事で紹介したいと思います。

ここまで見ていただきありがとうございました。次回もぜひ、よろしくお願いします。

Discussion