將 GenAI 整合到圖中
| 欄位 | 值 |
|---|---|
| 類別 | GenAI |
| 難度 | 進階 |
| 預估閱讀時間 | 20-25 minutes |
| 標籤 | genai, graph, composition, streaming, advanced |
大多數 GenAI 應用程式都應該從直接的模型 API 開始。當 GenAI 需要與其他 Neat 階段並列,例如命名輸入、命名輸出、路由或應用程式層級的協調時,圖的組合就會變得有用。
操作指南
建立 GenAI 圖片段
建立特定任務的模型處理器,設定圖片段選項,並建立一個公開的 Graph 片段。
視覺語言片段會公開 prompt、image 和 use_cached_image 輸入,以及 tokens、done、encoded 和 error 輸出。語音轉錄片段會公開 audio 和 audio_path 輸入,以及 tokens、done 和 error 輸出。
SpeechTranscriberOptions 預設為自動語言偵測和轉錄。在 C++ 中,將 task 設定為 ASRTask::Translate,或在 Python 中設定為 ASRTask.Translate,以將語音翻譯成英文。其 done 封包會報告偵測到的來源語言,並且在可用時,還會報告 no_speech_prob 和 avg_logprob。
auto model = std::make_shared<genai::VisionLanguageModel>(args.model);
genai::VisionLanguageOptions options;
options.system_prompt = "You are concise.";
options.max_new_tokens = 96;
options.streaming = true;
options.encode_images_on_input = false;
simaai::neat::Graph genai_fragment =
genai::graphs::VisionLanguage(model, options, "genai_stage");
將片段新增到應用程式圖
將片段新增到更大的應用程式圖中。該片段會保留其公開端點名稱,因此應用程式程式碼可以按名稱推送和提取。
simaai::neat::Graph app("genai_app");
app.add(genai_fragment);
std::cout << app.describe() << "\n";
建置並推送圖的輸入
將圖建置為 Run,將圖像樣本推送至 image 輸入,然後將文字樣本推送至 prompt 輸入,並讓 GenAI 階段產生 token。
simaai::neat::Run run = app.build();
if (!run.push("image", make_image_sample(args.image))) {
throw std::runtime_error("push(image) failed: " + run.last_error());
}
if (!run.push("prompt", make_text_sample("prompt", "Describe this image in one sentence."))) {
throw std::runtime_error("push(prompt) failed: " + run.last_error());
}
提取 token 和完成中繼資料
從 tokens 提取,直到收到 done 樣本為止。done 樣本是一個封包,其中包含已產生 token 數量和完成原因等欄位。
std::cout << "assistant: ";
for (int i = 0; i < 256; ++i) {
if (auto token = run.pull("tokens", 250)) {
std::cout << sample_text(*token) << std::flush;
continue;
}
if (auto done = run.pull("done", 10)) {
(void)done;
break;
}
if (auto error = run.pull("error", 10)) {
throw std::runtime_error(sample_text(*error));
}
}
std::cout << "\n";
run.close();
執行
在 Modalix DevKit 上,使用 LLiMa CLI 從 Hugging Face 下載 LFM2-VL 1.6B VLM:
llima pull LFM2-VL-1.6B-a16w4
在 Modalix 上執行教學,並使用 DevKit 本機模型目錄和本機圖像:
C++ (prebuilt):
./lib/sima-neat/tutorials/tutorial_022_compose_genai_into_graph \
--model /media/nvme/llima/models/LFM2-VL-1.6B-a16w4 \
--image share/sima-neat/tutorials/assets/fronalpstock_1330.jpg
C++ (build from source):
./build.sh --target tutorial_022_compose_genai_into_graph
./build/tutorials-standalone/tutorial_022_compose_genai_into_graph \
--model /media/nvme/llima/models/LFM2-VL-1.6B-a16w4 \
--image share/sima-neat/tutorials/assets/fronalpstock_1330.jpg
預期的輸出會顯示圖的描述,以及從 tokens 輸出中提取的串流式答案。
實務應用
當 GenAI 是大型應用程式圖的一部分時,請使用此模式。對於簡單的請求/回應應用程式程式碼,請保留對 GenAIModel、VisionLanguageModel 和 ASRModel 的直接呼叫。
完整原始碼
顯示完整原始碼程式
#include "neat.h"
#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc.hpp>
#include <filesystem>
#include <iostream>
#include <memory>
#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: compose_genai_into_graph --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;
}
simaai::neat::Sample make_text_sample(const std::string& port, const std::string& text) {
return simaai::neat::make_tensor_sample(port, simaai::neat::Tensor::from_text(text));
}
simaai::neat::Sample make_image_sample(const std::filesystem::path& image_path) {
cv::Mat bgr = cv::imread(image_path.string(), cv::IMREAD_COLOR);
if (bgr.empty()) {
throw std::runtime_error("failed to read image: " + image_path.string());
}
cv::Mat rgb;
cv::cvtColor(bgr, rgb, cv::COLOR_BGR2RGB);
return simaai::neat::make_tensor_sample(
"image", simaai::neat::Tensor::from_cv_mat(rgb, simaai::neat::ImageSpec::PixelFormat::RGB,
simaai::neat::TensorMemory::CPU));
}
std::string sample_text(const simaai::neat::Sample& sample) {
if (sample.kind == simaai::neat::SampleKind::Tensor && sample.tensor.has_value()) {
return sample.tensor->to_text();
}
if (sample.kind == simaai::neat::SampleKind::TensorSet && sample.tensors.size() == 1U) {
return sample.tensors.front().to_text();
}
return {};
}
int main(int argc, char** argv) {
try {
const Args args = parse_args(argc, argv);
auto model = std::make_shared<genai::VisionLanguageModel>(args.model);
genai::VisionLanguageOptions options;
options.system_prompt = "You are concise.";
options.max_new_tokens = 96;
options.streaming = true;
options.encode_images_on_input = false;
simaai::neat::Graph genai_fragment =
genai::graphs::VisionLanguage(model, options, "genai_stage");
simaai::neat::Graph app("genai_app");
app.add(genai_fragment);
std::cout << app.describe() << "\n";
simaai::neat::Run run = app.build();
if (!run.push("image", make_image_sample(args.image))) {
throw std::runtime_error("push(image) failed: " + run.last_error());
}
if (!run.push("prompt", make_text_sample("prompt", "Describe this image in one sentence."))) {
throw std::runtime_error("push(prompt) failed: " + run.last_error());
}
std::cout << "assistant: ";
for (int i = 0; i < 256; ++i) {
if (auto token = run.pull("tokens", 250)) {
std::cout << sample_text(*token) << std::flush;
continue;
}
if (auto done = run.pull("done", 10)) {
(void)done;
break;
}
if (auto error = run.pull("error", 10)) {
throw std::runtime_error(sample_text(*error));
}
}
std::cout << "\n";
run.close();
return 0;
} catch (const std::exception& e) {
std::cerr << "error: " << e.what() << "\n";
return 1;
}
}