「NABLA-VL」を試す
この度、当社は自社で開発した150億パラメーター規模の大規模視覚言語モデル(Vision-Language Model, VLM)「NABLA-VL」を公開したことをお知らせします。
本モデルは、経済産業省とNEDOが実施する、国内の生成AI開発力強化プロジェクト「GENIAC(Generative AI Accelerator Challenge)」で開発されたものです。「NABLA-VL」は、テキスト・画像・動画を理解する能力を備え、日本語、英語においてバランスの取れたバイリンガル対応且つ高性能マルチモーダルAIとして研究・産業の双方での利活用を目指しています。
Colaboratoryで軽く試してみた限り、L4だとVRAMに収まらない様子。今回はA100でやってみた。
レポジトリクローン
!git clone https://github.com/nablas-inc/NABLA-VL
%cd NABLA-VL
パッケージインストール
!pip install -e .
Flash Attentionをインストール。あと、今回は結局使わなかったが、量子化する場合はbitsandbytesをインストールしておく。
!pip install flash-attn --no-build-isolation
!pip install bitsandbytes
あと、モデルが結構重たいのかダウンロードにかなり時間がかかるので、hf_transferあたりのパッケージも更新しておく。
!pip install -U "huggingface_hub[cli]" hf_transfer
モデルはGated modelなので、事前にモデルカードのページで利用許諾に合意しておく必要がある。そして、HuggingFace CLIでトークンを入力してログイン。
!huggingface-cli login
モデルのダウンロード
!HF_HUB_ENABLE_HF_TRANSFER=1 huggingface-cli download nablasinc/NABLA-VL
このまま進めると、インストールしたパッケージがランタイムに反映されていなくて、ロードに失敗するので、ここで一旦ランタイム再起動しておく。
再起動後はパスがもとに戻ってしまうため、再度クローンしたディレクトリに移動。
%cd NABLA-VL
ではモデルカードの手順に従って、モデルをロードする。
import requests
from PIL import Image
import numpy as np
import torch
from transformers import AutoModel, AutoTokenizer
from nabla_vl.constants import CHAT_TEMPLATE_WITHOUT_SYSTEM_MESSAGE
from nabla_vl.inference import run_model_with_stream
from nabla_vl.io import load_image
from nabla_vl.model import NablaVLForCausalLM
from nabla_vl.transforms import build_data_pipeline
MODEL = "nablasinc/NABLA-VL"
DEVICE = "cuda"
model = NablaVLForCausalLM.from_pretrained(MODEL, torch_dtype=torch.bfloat16)
model.to(DEVICE)
model.eval()
tokenizer = AutoTokenizer.from_pretrained(MODEL, use_fast=False)
tokenizer.chat_template = CHAT_TEMPLATE_WITHOUT_SYSTEM_MESSAGE
data_pipeline = build_data_pipeline(model.config, tokenizer)
モデルロード後のVRAM消費は29GBと結構高め。
Sat May 31 08:00:21 2025
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 550.54.15 Driver Version: 550.54.15 CUDA Version: 12.4 |
|-----------------------------------------+------------------------+----------------------+
| GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. |
| | | MIG M. |
|=========================================+========================+======================|
| 0 NVIDIA A100-SXM4-40GB Off | 00000000:00:04.0 Off | 0 |
| N/A 33C P0 46W / 400W | 29327MiB / 40960MiB | 0% Default |
| | | Disabled |
+-----------------------------------------+------------------------+----------------------+
推論。まずはモデルカードのサンプルをそのまま実行。
instruction = "この画像について教えてください!"
images = []
urls = [
"https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/bee.jpg",
]
for url in urls:
images.append(
np.array(
Image.open(
requests.get(
url,
stream=True,
).raw,
).convert("RGB"),
)[np.newaxis, :, :, :],
)
run_model_with_stream(
model,
tokenizer,
data_pipeline,
instruction,
images=images,
device=DEVICE,
)
画像には、ピンクの花の上に小さな蜂が止まっている様子が写っています。この花は、他の花々と一緒に咲いており、周囲にはさまざまな色の花が見られます。特に、ピンクの花が目立っていますが、他にも赤い花も存在しています。
蜂は花の中心に位置しており、花の蜜を吸っているようです。周囲には他の花もあり、全体的に色とりどりの花々が広がっている美しい風景が広がっています。
再度nvidia-smiを見てみると、35GBぐらいまでVRAM消費が上がっていた。
Sat May 31 08:01:07 2025
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 550.54.15 Driver Version: 550.54.15 CUDA Version: 12.4 |
|-----------------------------------------+------------------------+----------------------+
| GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. |
| | | MIG M. |
|=========================================+========================+======================|
| 0 NVIDIA A100-SXM4-40GB Off | 00000000:00:04.0 Off | 0 |
| N/A 48C P0 253W / 400W | 35619MiB / 40960MiB | 97% Default |
| | | Disabled |
+-----------------------------------------+------------------------+----------------------+
ここからは自分の手持ちの画像などを使って試してみる。
神戸の風景画像
instruction = "この画像の場所を推論した上で画像について詳しく説明して。"
images = []
urls = [
"https://storage.googleapis.com/zenn-user-upload/82968d23b6c5-20250228.jpg",
]
for url in urls:
images.append(
np.array(
Image.open(
requests.get(
url,
stream=True,
).raw,
).convert("RGB"),
)[np.newaxis, :, :, :],
)
run_model_with_stream(
model,
tokenizer,
data_pipeline,
instruction,
images=images,
device=DEVICE,
)
この画像は、都市の港の風景を捉えています。水面にはいくつかのボートが浮かんでおり、その背後には高層ビルが立ち並んでいます。特に目を引くのは、赤い塔で、これはおそらく有名なランドマークです。この塔は、都市のシンボルとして知られている可能性があります。
港の周辺には、さまざまなボートが見え、その中には大きなボートも含まれています。ボートは水面に浮かび、都市の活気を感じさせます。また、港の近くには人々がいて、彼らはボートや周囲の景色を楽しんでいるようです。全体的に、この画像は都市の活気ある港の雰囲気を伝えています。
んー、神戸だと言うことは推論してくれなかった。
書籍の表紙
instruction = "この画像の書籍の表紙に書かれている文字を読み上げて。"
images = []
urls = [
"https://storage.googleapis.com/zenn-user-upload/caa14aef4b7e-20250226.png",
]
for url in urls:
images.append(
np.array(
Image.open(
requests.get(
url,
stream=True,
).raw,
).convert("RGB"),
)[np.newaxis, :, :, :],
)
run_model_with_stream(
model,
tokenizer,
data_pipeline,
instruction,
images=images,
device=DEVICE,
)
「コミュニケーションの全体像」上巻「原則と手順」中川氏
日本語の文字を読めてはいるのだが、色々プロンプトを変えてみたけども、細部までは読んでくれなかった。
(ダミーの)請求書の画像
instruction = "この画像の請求書の明細内容を読み上げて。"
images = []
urls = [
"https://storage.googleapis.com/zenn-user-upload/4697c3aef653-20250226.png",
]
for url in urls:
images.append(
np.array(
Image.open(
requests.get(
url,
stream=True,
).raw,
).convert("RGB"),
)[np.newaxis, :, :, :],
)
run_model_with_stream(
model,
tokenizer,
data_pipeline,
instruction,
images=images,
device=DEVICE,
)
この画像には、日本語で書かれた請求書が表示されています。請求書には、日付、請求番号、および請求内容が記載されています。請求書の詳細には、以下のような情報が含まれています:
- 請求書の日付は2024年8月20日
- 請求番号はINV-2024-0820
- 顧客名は「横浜製作所」
- 住所は「東京都千代田区」
- 電話番号は「03-1234-5678」
- ファックス番号は「03-1234-5679」
請求内容には、以下のアイテムが含まれています:
- サービスA: 1000単位、単価¥50、合計¥50,000
- サービスB: 20単位、単価¥2,000、合計¥40,000
- サービスC: 50単位、単価¥1,000、合計¥50,000
これらの合計金額は¥140,000で、消費税(10%)が¥14,000加算され、最終的な合計金額は¥154,000となっています。
明細の項目や顧客名がハルシネーションしてる。日本語の、特に小さな文字を読むのはあまり得意ではないのかも?
複数画像の場合
SmolVLM2を試したときにサンプルで用意されていた画像を拝借させてもらう
以下のデータセットに含まれた画像となっている
instruction = "これらの画像の違いについて説明してください"
images = []
urls = [
"https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/bee.jpg",
"https://huggingface.co/datasets/huggingface/documentation-images/resolve/0052a70beed5bf71b92610a43a52df6d286cd5f3/diffusers/rabbit.jpg"
]
for url in urls:
images.append(
np.array(
Image.open(
requests.get(
url,
stream=True,
).raw,
).convert("RGB"),
)[np.newaxis, :, :, :],
)
run_model_with_stream(
model,
tokenizer,
data_pipeline,
instruction,
images=images,
device=DEVICE,
)
この2つの画像は、異なるシーンを描いています。上の画像は、花の中にいる蜂を捉えており、ピンクの花が目立っています。この画像は自然の美しさを強調しており、蜂が花の蜜を吸っている様子が見られます。一方、下の画像は、人間のような服を着たウサギが描かれており、そのウサギは道の上に立っています。このウサギは、人間のような特徴を持っており、ユーモラスな印象を与えます。この2つの画像は、自然の生き物と人間の要素を組み合わせたユニークなコントラストを示しています。
動画もできる。こちらについてはGitHubレポジトリにコードがあった。
こちらもInternVL3を試したときの動画を拝借
!wget --content-disposition https://huggingface.co/OpenGVLab/InternVL3-2B/resolve/main/examples/red-panda.mp4?download=true
from nabla_vl.io import load_video
instruction = "この動画について時系列順にざっくり説明してください!"
images = [image[np.newaxis, :, :, :] for image in list(load_video("red-panda.mp4"))]
run_model_with_stream(
model,
tokenizer,
data_pipeline,
instruction,
images=images,
device=DEVICE,
)
このビデオは、赤いパンダが木の枝に座っている様子から始まります。赤いパンダは、木の上でリラックスしているように見え、その下には別の赤いパンダが立っています。次に、木の枝からぶら下がっているバナナが登場し、赤いパンダがそれを食べようとします。バナナを食べる様子が続き、赤いパンダはバナナをつかんで食べる姿が見られます。その後、赤いパンダはバナナを食べ終わった後、木の枝に戻り、リラックスした姿勢で座ります。最後に、赤いパンダは木の枝から降りて、地面に立つ姿が映し出されます。このように、ビデオは赤いパンダの日常的な行動を捉えています。
ちょっとVRAM消費が大きめだなぁ。bitsandbytesで4ビット量子化した場合はこんな感じ。
import requests
from PIL import Image
import numpy as np
import torch
from transformers import AutoModel, AutoTokenizer, BitsAndBytesConfig
from nabla_vl.constants import CHAT_TEMPLATE_WITHOUT_SYSTEM_MESSAGE
from nabla_vl.inference import run_model_with_stream
from nabla_vl.io import load_image
from nabla_vl.model import NablaVLForCausalLM
from nabla_vl.transforms import build_data_pipeline
quantization_config = BitsAndBytesConfig(
load_in_4bit=True
)
MODEL = "nablasinc/NABLA-VL" # Or where the checkpoint gets saved when you fine-tune a model
DEVICE = "cuda"
model = NablaVLForCausalLM.from_pretrained(
MODEL,
torch_dtype=torch.bfloat16,
quantization_config=quantization_config
)
model.to(DEVICE)
model.eval()
tokenizer = AutoTokenizer.from_pretrained(MODEL, use_fast=False)
tokenizer.chat_template = CHAT_TEMPLATE_WITHOUT_SYSTEM_MESSAGE
data_pipeline = build_data_pipeline(model.config, tokenizer)
Sat May 31 08:41:28 2025
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 550.54.15 Driver Version: 550.54.15 CUDA Version: 12.4 |
|-----------------------------------------+------------------------+----------------------+
| GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. |
| | | MIG M. |
|=========================================+========================+======================|
| 0 NVIDIA A100-SXM4-40GB Off | 00000000:00:04.0 Off | 0 |
| N/A 33C P0 47W / 400W | 14989MiB / 40960MiB | 0% Default |
| | | Disabled |
+-----------------------------------------+------------------------+----------------------+
何度か推論を繰り返してみた後はこんな感じ。
Sat May 31 08:44:53 2025
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 550.54.15 Driver Version: 550.54.15 CUDA Version: 12.4 |
|-----------------------------------------+------------------------+----------------------+
| GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. |
| | | MIG M. |
|=========================================+========================+======================|
| 0 NVIDIA A100-SXM4-40GB Off | 00000000:00:04.0 Off | 0 |
| N/A 48C P0 188W / 400W | 19661MiB / 40960MiB | 87% Default |
| | | Disabled |
+-----------------------------------------+------------------------+----------------------+
これならL4ランタイムでも収まりそう。