メインコンテンツまでスキップ

Direct API を使用して VLM を実行する

項目
カテゴリGenAI
難易度中級
推定所要時間10-15 minutes
ラベルgenai, vlm, image, cache, multimodal

ビジョン-言語モデルは、テキストと画像テンソルを受け入れることができます。1つの質問に対して、画像を GenerationRequest.images に直接添付します。繰り返し質問する場合は、画像を1回エンコードし、後続のリクエストでキャッシュされた画像埋め込みを再利用します。

ウォークスルー

VLM と画像をロードする

デプロイされた LLiMa モデルディレクトリから VisionLanguageModel をロードし、ディスクから画像をデコードします。

OpenCV を使用して画像を読み込みます。Neat は、3チャンネルの cv::Mat 入力を BGR として扱い、内部的に RGB に変換します。

tutorials/020_run_a_vlm/run_a_vlm.cpp
genai::VisionLanguageModel model(args.model);
cv::Mat image = cv::imread(args.image.string(), cv::IMREAD_COLOR);
if (image.empty()) {
throw std::runtime_error("failed to read image: " + args.image.string());
}

直接画像を添付して質問する

最初のリクエストに画像を直接添付します。これは最も簡単な方法であり、多くの場合、一度限りの視覚的な質問には十分です。

tutorials/020_run_a_vlm/run_a_vlm.cpp
genai::GenerationRequest direct;
direct.prompt = "Describe this image in one sentence.";
direct.images = {image};
direct.max_new_tokens = 96;

const genai::GenerationResult first = model.run(direct);
std::cout << "direct image: " << first.text << "\n\n";

画像埋め込みをキャッシュする

encode(...) を呼び出して、モデルに画像埋め込みをキャッシュします。この呼び出しは、画像が受け入れられ、キャッシュされた場合に true を返します。

tutorials/020_run_a_vlm/run_a_vlm.cpp
if (!model.encode(image)) {
throw std::runtime_error("VLM did not accept the image for caching");
}
std::cout << "cached_images=" << model.cached_image_count() << "\n";

後続の質問をする

キャッシュされた画像を使用する必要がある各リクエストで、use_cached_images = true を設定します。同じキャッシュされた画像について、複数の質問をすることができます。このフラグがないリクエストは通常どおりに動作します。テキストのみのリクエストでは画像は使用されず、直接画像リクエストでは独自の images が使用され、別の encode(...) 呼び出しによってキャッシュされた画像が置き換えられます。

tutorials/020_run_a_vlm/run_a_vlm.cpp
genai::GenerationRequest cached;
cached.prompt = "What details should I inspect more closely?";
cached.use_cached_images = true;
cached.max_new_tokens = 96;

const genai::GenerationResult follow_up = model.run(cached);
std::cout << "cached image: " << follow_up.text << "\n\n";

genai::GenerationRequest second_cached;
second_cached.prompt = "Summarize the image in three keywords.";
second_cached.use_cached_images = true;
second_cached.max_new_tokens = 48;

const genai::GenerationResult second_follow_up = model.run(second_cached);
std::cout << "cached image keywords: " << second_follow_up.text << "\n\n";

チャットメッセージに画像を添付する

messages を使用する場合は、必要なユーザーメッセージに画像を添付します。これにより、画像が関連する正確なテキストの隣に配置されます。

tutorials/020_run_a_vlm/run_a_vlm.cpp
genai::ChatMessage image_message;
image_message.role = "user";
image_message.content = "What is the main subject of this image?";
image_message.images = {image};

genai::GenerationRequest message_request;
message_request.messages = {image_message};
message_request.max_new_tokens = 96;

const genai::GenerationResult message_result = model.run(message_request);
std::cout << "message image: " << message_result.text << "\n";

実行

Modalix DevKit で、Hugging Face から LLiMa CLI を使用して LFM2-VL 1.6B VLM をダウンロードします。

llima pull LFM2-VL-1.6B-a16w4

Modalix で、DevKit ローカルモデルディレクトリとローカル画像を使用して、チュートリアルを実行します。

C++ (prebuilt):

./lib/sima-neat/tutorials/tutorial_020_run_a_vlm \
--model /media/nvme/llima/models/LFM2-VL-1.6B-a16w4 \
--image tests/images/people.jpg

C++ (build from source):

./build.sh --target tutorial_020_run_a_vlm
./build/tutorials-standalone/tutorial_020_run_a_vlm \
--model /media/nvme/llima/models/LFM2-VL-1.6B-a16w4 \
--image tests/images/people.jpg

期待される出力は、直接的な画像リクエストに対する1つの回答、キャッシュされた画像を再利用する複数のフォローアップ回答、およびメッセージレベルの画像リクエストに対する1つの回答です。

実践

ユーザーが同じフレーム、製品画像、図、またはドキュメントページについて複数の質問をする場合、画像キャッシュを使用します。各リクエストで異なる画像を使用する場合は、キャッシュを避けてください。なぜなら、直接的な画像パスの方がシンプルで、プロンプトの状態を明確に保つことができるからです。

一部のモデルファミリーでは、キャッシュされた画像の再利用がサポートされていない場合があります。その場合は、各リクエストで直接的な画像を使用してください。

会話を構築し、1つのメッセージにのみ画像を含める必要がある場合は、ChatMessage.imagesを使用します。よりシンプルな単一プロンプト形式の場合は、最上位レベルのGenerationRequest.imagesを使用します。

完全なソース

完全なソースプログラムを表示
tutorials/020_run_a_vlm/run_a_vlm.cpp
#include "neat/genai.h"

#include <opencv2/imgcodecs.hpp>

#include <filesystem>
#include <iostream>
#include <stdexcept>
#include <string>

namespace genai = simaai::neat::genai;

struct Args {
std::filesystem::path model;
std::filesystem::path image;
};

Args parse_args(int argc, char** argv) {
Args args;
for (int i = 1; i < argc; ++i) {
const std::string arg = argv[i];
if (arg == "--model" && i + 1 < argc) {
args.model = argv[++i];
} else if (arg == "--image" && i + 1 < argc) {
args.image = argv[++i];
} else {
throw std::runtime_error("usage: run_a_vlm --model <vlm_model_dir> --image <image>");
}
}
if (args.model.empty() || args.image.empty()) {
throw std::runtime_error("missing required --model <vlm_model_dir> or --image <image>");
}
return args;
}

int main(int argc, char** argv) {
try {
const Args args = parse_args(argc, argv);

genai::VisionLanguageModel model(args.model);
cv::Mat image = cv::imread(args.image.string(), cv::IMREAD_COLOR);
if (image.empty()) {
throw std::runtime_error("failed to read image: " + args.image.string());
}

genai::GenerationRequest direct;
direct.prompt = "Describe this image in one sentence.";
direct.images = {image};
direct.max_new_tokens = 96;

const genai::GenerationResult first = model.run(direct);
std::cout << "direct image: " << first.text << "\n\n";

if (!model.encode(image)) {
throw std::runtime_error("VLM did not accept the image for caching");
}
std::cout << "cached_images=" << model.cached_image_count() << "\n";

genai::GenerationRequest cached;
cached.prompt = "What details should I inspect more closely?";
cached.use_cached_images = true;
cached.max_new_tokens = 96;

const genai::GenerationResult follow_up = model.run(cached);
std::cout << "cached image: " << follow_up.text << "\n\n";

genai::GenerationRequest second_cached;
second_cached.prompt = "Summarize the image in three keywords.";
second_cached.use_cached_images = true;
second_cached.max_new_tokens = 48;

const genai::GenerationResult second_follow_up = model.run(second_cached);
std::cout << "cached image keywords: " << second_follow_up.text << "\n\n";

genai::ChatMessage image_message;
image_message.role = "user";
image_message.content = "What is the main subject of this image?";
image_message.images = {image};

genai::GenerationRequest message_request;
message_request.messages = {image_message};
message_request.max_new_tokens = 96;

const genai::GenerationResult message_result = model.run(message_request);
std::cout << "message image: " << message_result.text << "\n";

return 0;
} catch (const std::exception& e) {
std::cerr << "error: " << e.what() << "\n";
return 1;
}
}

ソース