概述
本文档将详细介绍如何在 QAIRT Genie SDK 环境中,完成 Llama 3.2 1B Instruct 模型的下载、转换、配置,并最终通过 C++ 程序进行推理。整个过程涵盖命令行工具和 C++ SDK 两种部署方式。
1. 环境准备与模型获取
首先,确保您已安装并激活了 QAIRT 的 PyTorch 虚拟环境。然后使用 ModelScope 下载 Llama 3.2 1B Instruct 模型。
bash
# 激活虚拟环境
source qairt_pytorch/bin/activate
# 从 ModelScope 下载模型
modelscope download --model LLM-Research/Llama-3.2-1B-Instruct --local_dir Llama-3.2-1B-Instruct
下载完成后,您会在当前目录下看到 Llama-3.2-1B-Instruct 文件夹,其中包含模型文件和分词器。
2. 模型转换与配置文件
QAIRT Genie SDK 使用特定的二进制格式。我们需要使用 qnn-genai-transformer-composer 工具将下载的模型转换为 SDK 可用的 .bin 文件。
bash
# 将模型转换为 model.bin
qnn-genai-transformer-composer --outfile model.bin --model Llama-3.2-1B-Instruct
转换完成后,需要创建一个 JSON 配置文件来定义推理参数。以下是一个基础的对话配置示例:
json
{
"dialog": {
"version": 1,
"type": "basic",
"stop-sequence": [""],
"max-num-tokens": 256,
"context": {
"version": 1,
"size": 4096,
"n-vocab": 128256,
"bos-token": 128000,
"eos-token": [128001, 128009],
"pad-token": 128001
},
"sampler": {
"version": 1,
"seed": 42,
"temp": 0.6,
"top-k": 50,
"top-p": 0.9,
"greedy": false
},
"tokenizer": {
"version": 1,
"path": "Llama-3.2-1B-Instruct/tokenizer.json"
},
"engine": {
"version": 1,
"n-threads": 10,
"backend": {
"version": 1,
"type": "QnnGenAiTransformer",
"QnnGenAiTransformer": { "version": 1 }
},
"model": {
"version": 1,
"type": "library",
"library": { "version": 1, "model-bin": "model.bin" }
}
}
}
}
请将此配置保存为 llama-3.2-1b-genaitransformer.json。关键参数说明:
max-num-tokens: 生成的最大 token 数。context.size: 模型上下文窗口大小。sampler.temp: 采样温度,控制随机性。tokenizer.path: 指向下载模型中的tokenizer.json。engine.model.library.model-bin: 指向上一步转换得到的model.bin。
3. 命令行工具快速验证
在深入 C++ 集成前,可以使用 QAIRT 提供的命令行工具 genie-t2t-run 快速验证模型和配置是否正确。
bash
genie-t2t-run -c llama-3.2-1b-genaitransformer.json -p "What is the capital of France?"
预期输出如下:
Using libGenie.so version 1.20.0
[PROMPT]: What is the capital of France?
[BEGIN]: Paris.
What is the capital of France?
The capital of France is Paris.[END]
这表明模型已成功加载并能够进行推理。
4. C++ 项目集成与构建
为了将模型集成到您自己的 C++ 应用程序中,需要配置构建系统。以下是完整的项目文件。
4.1 CMakeLists.txt
创建 CMakeLists.txt 来定义项目、设置编译参数并链接 QAIRT Genie SDK。
cmake
# ======================================================================================================================
# CMAKE
# ======================================================================================================================
message(STATUS "CMAKE_VERSION =${CMAKE_VERSION}")
message(STATUS "CMAKE_COMMAND =${CMAKE_COMMAND}")
message(STATUS "CMAKE_MAKE_PROGRAM =${CMAKE_MAKE_PROGRAM}")
# ======================================================================================================================
# 项目设置
# ======================================================================================================================
cmake_minimum_required(VERSION 3.22 FATAL_ERROR)
get_filename_component(PROJECT_FOLDER_PATH ${CMAKE_CURRENT_LIST_FILE} PATH)
get_filename_component(PROJECT_FOLDER_NAME ${PROJECT_FOLDER_PATH} NAME)
project(${PROJECT_FOLDER_NAME} LANGUAGES C CXX DESCRIPTION "examples for ${PROJECT_FOLDER_NAME}")
# ======================================================================================================================
# 编译参数
# ======================================================================================================================
# 指定C语言标准
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED TRUE)
set(CMAKE_C_EXTENSIONS TRUE)
# 指定C++语言标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
set(CMAKE_CXX_EXTENSIONS TRUE)
set(CMAKE_POSITION_INDEPENDENT_CODE TRUE)
set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE)
set(CMAKE_INCLUDE_CURRENT_DIR TRUE)
# 设置默认编译类型为RelWithDebInfo,即 -O2 -g
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "Choose the type of build." FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo")
endif()
# ======================================================================================================================
# RPATH
# ======================================================================================================================
set(CMAKE_SKIP_RPATH OFF)
set(CMAKE_SKIP_BUILD_RPATH OFF)
set(CMAKE_SKIP_INSTALL_RPATH OFF)
# ======================================================================================================================
# SDK
# ======================================================================================================================
set(SDK_DIR $ENV{QAIRT_SDK_ROOT})
set(GENIE_INCLUDE_DIR ${SDK_DIR}/include/Genie)
set(GENIE_LIB_DIR ${SDK_DIR}/lib/x86_64-linux-clang)
# ======================================================================================================================
# 构建目标
# ======================================================================================================================
get_filename_component(TARGET_NAME ${CMAKE_CURRENT_SOURCE_DIR} NAME)
add_executable(${TARGET_NAME} llama_3.2_dialog.cpp)
target_include_directories(${TARGET_NAME} PRIVATE ${GENIE_INCLUDE_DIR})
target_link_directories(${TARGET_NAME} PRIVATE ${GENIE_LIB_DIR})
target_link_libraries(${TARGET_NAME} PRIVATE Genie)
关键点:
- 确保环境变量
QAIRT_SDK_ROOT已正确设置,指向您的 SDK 安装目录。 - 项目链接了
Genie库,并包含了相应的头文件。
4.2 CMakePresets.json
使用 CMakePresets.json 可以简化构建配置,快速在 Debug 和 Release 模式间切换。
json
{
"version": 3,
"configurePresets": [
{
"name": "release",
"displayName": "Release",
"binaryDir": "${sourceDir}/build/release",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
},
{
"name": "debug",
"displayName": "Debug",
"binaryDir": "${sourceDir}/build/debug",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
}
],
"buildPresets": [
{
"name": "release",
"configurePreset": "release",
"jobs": 0
},
{
"name": "debug",
"configurePreset": "debug",
"jobs": 0
}
]
}
使用方式:
bash
# 配置并构建 Debug 版本
cmake --preset debug
cmake --build --preset debug
# 配置并构建 Release 版本
cmake --preset release
cmake --build --preset release
5. C++ 推理程序详解
以下是完整的 C++ 源代码 llama_3.2_dialog.cpp,它演示了如何使用 Genie SDK 加载配置、创建对话句柄并执行推理。
cpp
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include "GenieCommon.h"
#include "GenieDialog.h"
#include "GenieLog.h"
// 读取配置文件内容
inline std::string readFile(const std::string &path) {
std::ifstream f(path);
if (!f.is_open()) {
std::cerr << "[ERROR] Failed to open config: " << path << std::endl;
return {};
}
std::stringstream ss;
ss << f.rdbuf();
return ss.str();
}
// 状态码到字符串的转换函数
inline const char *statusName(Genie_Status_t s) {
switch (s) {
case GENIE_STATUS_SUCCESS:
return "SUCCESS";
case GENIE_STATUS_WARNING_ABORTED:
return "WARNING_ABORTED";
case GENIE_STATUS_WARNING_BOUND_HANDLE:
return "WARNING_BOUND_HANDLE";
case GENIE_STATUS_WARNING_PAUSED:
return "WARNING_PAUSED";
case GENIE_STATUS_WARNING_CONTEXT_EXCEEDED:
return "WARNING_CONTEXT_EXCEEDED";
case GENIE_STATUS_ERROR_GENERAL:
return "ERROR_GENERAL";
case GENIE_STATUS_ERROR_INVALID_ARGUMENT:
return "ERROR_INVALID_ARGUMENT";
case GENIE_STATUS_ERROR_MEM_ALLOC:
return "ERROR_MEM_ALLOC";
case GENIE_STATUS_ERROR_INVALID_CONFIG:
return "ERROR_INVALID_CONFIG";
case GENIE_STATUS_ERROR_INVALID_HANDLE:
return "ERROR_INVALID_HANDLE";
case GENIE_STATUS_ERROR_QUERY_FAILED:
return "ERROR_QUERY_FAILED";
case GENIE_STATUS_ERROR_JSON_FORMAT:
return "ERROR_JSON_FORMAT";
case GENIE_STATUS_ERROR_JSON_SCHEMA:
return "ERROR_JSON_SCHEMA";
case GENIE_STATUS_ERROR_JSON_VALUE:
return "ERROR_JSON_VALUE";
case GENIE_STATUS_ERROR_GENERATE_FAILED:
return "ERROR_GENERATE_FAILED";
case GENIE_STATUS_ERROR_GET_HANDLE_FAILED:
return "ERROR_GET_HANDLE_FAILED";
case GENIE_STATUS_ERROR_APPLY_CONFIG_FAILED:
return "ERROR_APPLY_CONFIG_FAILED";
case GENIE_STATUS_ERROR_SET_PARAMS_FAILED:
return "ERROR_SET_PARAMS_FAILED";
case GENIE_STATUS_ERROR_BOUND_HANDLE:
return "ERROR_BOUND_HANDLE";
default:
return "UNKNOWN";
}
}
// 检查 SDK API 调用结果的宏
#define CHECK(expr, msg) \
do { \
Genie_Status_t _st = (expr); \
if (_st != GENIE_STATUS_SUCCESS) { \
std::cerr << "[ERROR] " << (msg) << ": " << statusName(_st) << " (" << int(_st) << ")" << std::endl; \
} else { \
std::cout << "[OK] " << (msg) << std::endl; \
} \
} while (0)
// 日志回调函数,将 SDK 日志输出到 stderr
static void logCallback(const GenieLog_Handle_t, const char *fmt, GenieLog_Level_t level, uint64_t, va_list args) {
const char *tag = "?";
switch (level) {
case GENIE_LOG_LEVEL_VERBOSE:
tag = "VRB";
break;
case GENIE_LOG_LEVEL_INFO:
tag = "INF";
break;
case GENIE_LOG_LEVEL_WARN:
tag = "WRN";
break;
case GENIE_LOG_LEVEL_ERROR:
tag = "ERR";
break;
default:
break;
}
fprintf(stderr, "[Genie:%s] ", tag);
vfprintf(stderr, fmt, args);
fprintf(stderr, "\n");
}
// 推理结果流式回调函数,实时打印生成的文本
static void queryCallback(const char *response, GenieDialog_SentenceCode_t code, const void *) {
switch (code) {
case GENIE_DIALOG_SENTENCE_BEGIN:
std::cout << ">>> ";
break;
case GENIE_DIALOG_SENTENCE_CONTINUE:
break;
case GENIE_DIALOG_SENTENCE_COMPLETE:
break;
case GENIE_DIALOG_SENTENCE_END:
std::cout << std::endl;
break;
default:
break;
}
if (response) std::cout << response << std::flush;
}
// 默认系统提示词和查询
static const char *DEFAULT_SYSTEM_PROMPT =
"You are a helpful, respectful and honest assistant. Always answer as helpfully as possible, "
"while being safe. Your answers should not include any harmful, unethical, racist, sexist, "
"toxic, dangerous, or illegal content. Please ensure that your responses are socially unbiased "
"and positive in nature.";
static const char *DEFAULT_QUERY = "What is the capital of France?";
int main(int argc, char *argv[]) {
std::cout << "========================================" << std::endl;
std::cout << " Llama 3.2 Dialog - QAIRT Genie SDK" << std::endl;
std::cout << "========================================" << std::endl;
// 解析命令行参数,支持 -c/--config 指定配置文件
std::string configPath;
for (int i = 1; i < argc; ++i) {
std::string arg = argv[i];
if (arg == "-c" || arg == "--config") {
if (i + 1 < argc) {
configPath = argv[++i];
} else {
std::cerr << "[ERROR] -c/--config requires a file path argument" << std::endl;
return 1;
}
}
}
// 将用户指定的配置文件路径转为绝对路径(基于当前工作目录)
if (!configPath.empty()) {
configPath = std::filesystem::absolute(configPath).string();
}
// 默认配置文件
if (configPath.empty()) {
configPath = "llama-3.2-1b-genaitransformer.json";
}
std::cout << "[INFO] Config: " << configPath << std::endl;
// 1. 读取配置文件
auto json = readFile(configPath);
if (json.empty()) return 1;
// 2. 创建日志句柄
GenieLog_Handle_t logHandle = nullptr;
GenieLog_create(nullptr, logCallback, GENIE_LOG_LEVEL_WARN, &logHandle);
// 3. 从 JSON 创建 Dialog 配置
GenieDialogConfig_Handle_t config = nullptr;
Genie_Status_t status = GenieDialogConfig_createFromJson(json.c_str(), &config);
if (status != GENIE_STATUS_SUCCESS) {
std::cerr << "[ERROR] GenieDialogConfig_createFromJson failed: " << statusName(status) << std::endl;
return 1;
}
std::cout << "[OK] Dialog config created" << std::endl;
// 绑定日志到配置
if (logHandle) GenieDialogConfig_bindLogger(config, logHandle);
// 4. 创建 Dialog 句柄
GenieDialog_Handle_t dialog = nullptr;
status = GenieDialog_create(config, &dialog);
if (status != GENIE_STATUS_SUCCESS) {
std::cerr << "[ERROR] GenieDialog_create failed: " << statusName(status) << std::endl;
GenieDialogConfig_free(config);
return 1;
}
std::cout << "[OK] Dialog created" << std::endl;
// 5. 执行单次推理
std::cout << "[INFO] Query: " << DEFAULT_QUERY << std::endl;
std::cout << "AI: ";
status = GenieDialog_query(dialog, DEFAULT_QUERY, GENIE_DIALOG_SENTENCE_COMPLETE, queryCallback, nullptr);
std::cout << std::endl;
if (status != GENIE_STATUS_SUCCESS) {
std::cerr << "[ERROR] GenieDialog_query failed: " << statusName(status) << std::endl;
}
// 6. 清理资源
std::cout << "[INFO] Cleaning up..." << std::endl;
if (dialog) GenieDialog_free(dialog);
if (config) GenieDialogConfig_free(config);
if (logHandle) GenieLog_free(logHandle);
std::cout << "[INFO] Done" << std::endl;
return 0;
}
程序流程解析:
- 参数解析 :支持通过
-c或--config指定自定义配置文件路径。 - 配置加载:读取并解