Phi-3-vision をtransformers + MacのGPUで動かしてみた

Microsoftから公開されたPhi-3-visionをTransformers + MacGPUを使って動かしました。

Phi-3-visionとは

Microsoftが公開してる小規模言語モデルの中でマルチモーダル対応されたモデルです。他のPhi-3のモデル同様、パラメータ数4.2Bという一般的に公開されているモデルと比べると少ないパラメータでありながら、高い精度で動作するモデルです。

azure.microsoft.com

準備

モデルのダウンロード

以下からモデルのダウンロードを行います。

huggingface.co

flash-attentionの無効化

Macwindowsではflash-attentionを利用することができないため、以下のrunning-on-windows-or-without-flash-attentionの内容に従ってファイルを編集します。

https://huggingface.co/microsoft/Phi-3-vision-128k-instruct#running-on-windows-or-without-flash-attention

modeling_phi3_v.pyの以下の内容をコメントアウトします。

# if is_flash_attn_2_available():
#     from flash_attn import flash_attn_func, flash_attn_varlen_func
#     from flash_attn.bert_padding import index_first_axis, pad_input, unpad_input  # noqa

#     _flash_supports_window_size = "window_size" in list(inspect.signature(flash_attn_func).parameters)

実行用コードの準備

実行のためにPhi-3-visionのモデルがあるディレクトリに以下のコードをmain.pyとして保存します。 実行にはGradioが必要です。

import gradio as gr
import numpy as np
from PIL import Image
import torch
from transformers import AutoModelForCausalLM
from transformers import AutoProcessor

device = torch.device('mps')

model_name = "./"

processor = AutoProcessor.from_pretrained(model_name, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(model_name, trust_remote_code=True, torch_dtype="auto", _attn_implementation="eager").to(device)

user_prompt = '<|user|>\n'
assistant_prompt = '<|assistant|>\n'
prompt_suffix = "<|end|>\n"

def generate_response(input_text, input_image):

    prompt = f"{user_prompt}"
    images = None

    if isinstance(input_image, np.ndarray):
        prompt += f"<|image_1|>\n"
        images = [Image.fromarray(input_image)]
    elif isinstance(input_image, Image.Image):
        prompt += f"<|image_1|>\n"
        images = [input_image]

    if input_text != "":
        prompt += f"{input_text}"
    
    prompt += f"{prompt_suffix}{assistant_prompt}"
    print(f">>> Prompt\n{prompt}")

    inputs = processor(prompt, images=images, return_tensors="pt").to(device)

    generate_ids = model.generate(**inputs, 
                                max_new_tokens=1000,
                                eos_token_id=processor.tokenizer.eos_token_id,
                                )
    
    generate_ids = generate_ids[:, inputs['input_ids'].shape[1]:]

    response = processor.batch_decode(generate_ids, 
                                    skip_special_tokens=True, 
                                    clean_up_tokenization_spaces=False)[0]
    
    print(f'>>> Response\n{response}')

    return response

def main():
    with gr.Blocks() as page:
        image = gr.Image(type="pil")
        input_text = gr.Textbox()
        output_text = gr.TextArea(label="decoded text")
        gr.Interface(fn=generate_response, inputs=[input_text, image], outputs=output_text)
    page.launch(inbrowser=True)

if __name__=="__main__":
    main()

cudaを利用できる環境の場合は以下のように書き換えてください。

device = torch.device('cuda')

実行環境

  • OS:MacOS Sonoma
  • SoC:M3 Pro (CPU 12C GPU 18C)
  • RAM:18GB
  • Python:3.11.9
  • Torch:2.4.0dev
  • Transformers:4.40.0

実行結果

テキストのみを入力するケースとテキストと画像を入力するケースを試しました。

テキストのみの実行では以下のレスポンスの生成に270秒かかりました。

テキストのみ

また、画像を含めた生成の場合も以下のレスポンス生成に250秒かかりました。

テキストと画像

Macで実行した場合は生成にかなり時間を要するため実用するのは難しいと感じました。

windows + CUDAの環境では、Flash-Attentionが無効の状態でも30秒ほどで以下のような出力を得ることができました。

テキストと画像その2

終わりに

Phi-3-visionは小型軽量でありながら精度の高いマルチモーダルモデルだと感じました。画像を入力できるようになったことでより幅広いタスクへの利用が可能になりました。 WIndowsでの動作やLinuxFlash-Attentionの利用を有効にした状態での動作も確認していきたいです。