👀

ローカル環境で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)

内容

今回の記事では、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