当前位置: 首页 > news >正文

在android 系统上qnn sdk转换,运行模型示例

前面讲了如何配置qnn sdk的环境,这一篇总结下qnn 实际转换一个onnx 模型,并运行的实现步骤。

设备:

1. ubuntu22.04 的Linux 服务器。

2. 一台android手机。

一、下载模型

from optimum.onnxruntime import ORTModelForSequenceClassification
from transformers import AutoTokenizerdef download_model():model_path = "BAAI/bge-base-zh-v1.5"save_directory = "./bge-base-zh-v1.5.onnx"# 从 Transformers 加载模型并将其导出为 ONNXmodel = ORTModelForSequenceClassification.from_pretrained(model_path, export=True)tokenizer = AutoTokenizer.from_pretrained(model_path)# 保存 ONNX 模型和分词器model.save_pretrained(save_directory)tokenizer.save_pretrained(save_directory)

Check模型和简化模型,固定shape.方便后续的处理。

onnxsim ./bge-model.onnx bge-simple2-model.onnx --overwrite-input-shape inputs_ids:1,512 --overwrite-input-shape attention_mask:1,512  --overwrite-input-shape token_type_ ids:1,512

 二、构建量化数据

因为真实数据集对于构建量化是非常重要的,量化的质量直接影响到模型的推理结果准确性,使用的校准数据应该最大程度上接近训练和测试的数据集,这样子测试的效果更加的贴合实际业务场景的测试结果。

Embedding模型的主要测试数据集通常包括多种语言和任务类型的数据集,以全面评估模型的性能。例如,MTEB(Massive Text Embedding Benchmark)是一个大规模的文本嵌入基准测试,旨在全面评估文本嵌入方法的性能。MTEB 覆盖了 8 种嵌入任务,包含 58 个数据集和 112 种语言。此外,C-MTEB 是专门针对中文文本向量的评测基准,共收集了35个公共数据集,分为6类的评估任务。这些数据集可以帮助评估Embedding模型在不同语言和任务上的表现。所以我们选择C-MTEB(Chinese Massive Text Embedding Benchmark)测试中使用的LCQMC数据集进行构建量化需要的校准数据。

  1. 使用python 加载C-MTEB/LCQMC数据集。

  2. 使用tokener进行文本的词化。

  3. 将数据保存成三个输入,格式是raw,也就是原始的二进制文件。

def create_lcqmc_quant_data():dataset = load_dataset("C-MTEB/LCQMC", split="test")sentence = dataset["sentence1"]model = AutoModel.from_pretrained("BAAI/bge-base-zh-v1.5")tokenizer = AutoTokenizer.from_pretrained("./")#开启评估模式model.eval()calib_data_size = 100input_data = tokenizer(sentence, padding="max_length", truncation=True, max_length=512, return_tensors="pt")input_ids = input_data["input_ids"].numpy().astype(np.int32)[:calib_data_size, :]attention_mask = input_data["attention_mask"].numpy().astype(np.int32)[:calib_data_size, :]token_type_ids = input_data["token_type_ids"].numpy().astype(np.int32)[:calib_data_size, :]calib_data_dir = "calib_data"os.makedirs(calib_data_dir)path_lists = []path_file = "input_list.txt"# one for funcation to iter multi arrfor index, (arr, name) in enumerate(zip([input_ids, attention_mask, token_type_ids], ["input_ids", "attention_mask", "token_type_ids"])):# iter rows.# iter input_id, then mask, last token_idfor i in range(arr.shape[0]):# one arr save in one file.file_path = os.path.join(calib_data_dir, f"{name}_{i}.raw")#sava path.path_lists.append(file_path)## save data.arr[i].tofile(file_path)with open(path_file, "w", encoding="utf-8") as f:len = input_ids.shape[0]print(3*input_ids.shape[0])for i in range(input_ids.shape[0]):# one raw contain three input data file path.f.write(f"{os.path.abspath(path_lists[i])} {os.path.abspath(path_lists[i + len])} {os.path.abspath(path_lists[i + 2*len])}\n")print("create quant data finish.")

三、构建模型的运行输入数据

def create_model_input_data():model = AutoModel.from_pretrained("BAAI/bge-base-zh-v1.5")tokenizers = AutoTokenizer.from_pretrained("BAAI/bge-base-zh-v1.5")input_data = "ZhongGuo, nihao, 日本再见, good cat!"model.eval()current_path = os.path.abspath(__file__)dir_name = os.path.dirname(current_path)print(dir_name)real_data = "real_data"os.makedirs(real_data)input_ids_path = os.path.join(dir_name, "real_data" + "\\"  + "input_ids.raw")attention_mask_path = os.path.join(dir_name,   "real_data" + "\\" + "attention_mask.raw")token_type_ids_path = os.path.join(dir_name,  "real_data" + "\\" + "token_type_ids.raw")## get inputinput_tensor_data = tokenizers(input_data, padding="max_length", truncation=True, max_length=512, return_tensors="pt" )input_tensor_data["input_ids"].numpy().astype(np.int32).tofile(input_ids_path)input_tensor_data["attention_mask"].numpy().astype(np.int32).tofile(attention_mask_path)input_tensor_data["token_type_ids"].numpy().astype(np.int32).tofile(token_type_ids_path)print(input_ids_path)path_list = [input_ids_path + " ", attention_mask_path + " ", token_type_ids_path + " "]with open("./input_data.txt", "w", encoding="utf-8") as f:f.writelines(path_list)

四、运行onnx  模型得到基准数据

def run_bge_model():model = AutoModel.from_pretrained("BAAI/bge-base-zh-v1.5")tokenizers = AutoTokenizer.from_pretrained("BAAI/bge-base-zh-v1.5")input_data =  "ZhongGuo, nihao, 日本再见, good cat!"device = "cuda" if torch.cuda.is_available() else "cpu"model.to(device)model.eval()input_tensor_data = tokenizers(input_data, padding="max_length", truncation=True, max_length=512, return_tensors="pt" ).to(device)with torch.no_grad():output = model(**input_tensor_data)output_data = output.last_hidden_state.flatten().tolist()[:100]print(len(output.last_hidden_state.flatten().tolist()))print(output_data)

五、转换onnx 模型得到模型文件和权重数据。

 python /home/shengqing.liu/qaisw-v2.12.0.230626174329_59328-auto/bin/x86_64-linux-clang/qnn-onnx-converter -o ./bge_real_data_qnn.cpp -i ./bge-simple-model.onnx  --input_list ./input_list_calib.txt

转换成得到了三个文件bge_real_data_qnn.bin, bge_real_data_qnn.cpp, bge_real_data_qnn_net.json. 这三个文件就是qnn的模型文件。

.bin文件: 这个保存了qnn模型的参数。

.cpp文件:这个文件描述了qnn 模型结构的api 文件,依据这个文件可以创建qnn的计算图,Tool utility api 是用于生成 QNN API 调用的辅助模块。这些 API 是core QNN API 之上的轻量级包装器,旨在减少创建 QNN 图的重复步骤。

.json 文件:这个json 文件也是描述了qnn的模型结构文件,只是用来辅助描述模型结构文件的,这个文件后续的步骤中不会再使用到。

注意:

1. 将量化数据文件的绝对路径写入到input_list.txt中,相对路径是不行的,到时模型转换的时候会报错。

2. 加入--input_list 会使用默认的量化W8A8 进行量化模型,参看官方文档,查看支持的量化方法是min/max, 精度其实很差,基于经验,qnn的量化基本不可使用,勉强能用的W8A16 还要看特定的业务,一般也是精度达不到要求的。w16A16的量化需要芯片架构大于V73的才支持。

3. 设置其他的量化精度使用参数比如W8A16 --act_bw 16 --weight_bw 8

4. 实际中使用的都是半精度FP16 , 在转换的时候去除--input_list 参数,加上--float_bw 16 就是半精度模型了。类似这种

python ${QNN_SDK_ROOT_27}/bin/x86_64-linux-clang/qnn-onnx-converter -o ./bge_qnn_27_fp16_noquant.cpp -i ./bge-simple-model.onnx --float_bw 16

5. 半精度模型运行时候,加上--use_native_input_files ,  精度会好很多,实际中需要加上这个参数。

六、得到动态库的so模型文件

将cpp 和bin 文件作为输入文件,使用qnn-model-lib-generator转换成so 库文件。得到了Android和linux 平台的so 文件,这个文件其实是动态加载模型文件,这个so 也可以进行运行,但是初始化的时间需要较长,当进一步转换成bin文件,进行编译优化的时候,推理运行时加载准备时间就大大减少。

 ${QNN_SDK_ROOT}/bin/x86_64-linux-clang/qnn-model-lib-generator -c ./bge_real_data_qnn.cpp -b ./bge_real_data_qnn.bin -o rag_qnn_so

得到so 模型文件,这两个目录下的so 都是可以直接运行的qnn 模型文件,android 目录下的是可以直接在android 系统运行的,linux 下的是在linux 平台运行的,并且在转bin 文件的时候,在linux 平台使用这个linux 目录下的so 转出来的bin, 可以在android 设备htp等后端运行,这点注意。

 七、进一步编译得到bin 模型文件

我们使用htp 后端进行推理,可以构建一个后端backend配置文件,然后将配置文件作为输入,转换得到和硬件相关联的bin 文件,类似于c++ 代码实现中的构建context的那一步。

 ${QNN_SDK_ROOT}/bin/x86_64-linux-clang/qnn-context-binary-generator --model ./output-so/x86_64-linux-clang/libbge_real_data_qnn.so --backend ${QNN_SDK_ROOT}/lib/x86_64-linux-clang/libQnnHtp.so --output_dir ./bin --binary_file bge_8295_qnn.serialized --config_file ./htp_backend_extension.json

配置文件如下,我配置的文件如下:

htp_backend_extension.json

{ "backend_extensions":{"shared_library_path":"/home/shengqing.liu/qaisw-v2.12.0.230626174329_59328-auto/lib/x86_64-linux-clang/libQnnHtpNetRunExtensions.so","config_file_path": "/home/shengqing.liu/model/rag-bge-base/htp_device_config.json"}
}

htp_device_config.json

{"graphs":{"vtcm_mb":8,  // 片上内存设置"O":3.0,     // 等级有1.0, 2.0, 3.0 , 3.0优化最大"fp16_relaxed_precision":1,  // 支持fp16计算"graph_names":["bge_smiply_qnn"]  //生成的so文件名中去除lib,就是图名称。},"devices":[{"device_id":0,    //npu的device id, 可以不设置"soc_id":39,      // 芯片系统的id."dsp_arch":"v68",   // 芯片的架构"cores":[{"core_id":0,"perf_profile":"burst"  //性能等级,这个是很快的设置,还有其他的。}]}]
}

sdk2.20 以后定义图配置是使用图数组,之前的版本是使用图对象的格式,所以定义图数组之后,会出现找不到key的错误。

 八、运行模型

1. Linux平台运行

${QNN_SDK_ROOT}/bin/x86_64-linux-clang/qnn-net-run --backend ${QNN_SDK_ROOT}/lib/x86_64-linux-clang/libQnnHtp.so --model ./rag_qnn_so/x86_64-linux-clang/libreal_data_qnn.so --input_list ./input_list.txt

2. android 系统运行

将qnn的so库, 模型,数据,可执行文件push 到android系统中。

adb shell mkdir -p /data/local/tmp/bge//模型文件adb push D:\work_project\2025\bge\real_data_quant\bin\bge_8295_qnn.serialized.bin  /data/local/tmp/bge_model
//输入数据
adb push D:\work_project\2025\bge\real_data_quant\real_input_data\* /data/local/tmp/bge
//输入数据地址
adb push D:\work_project\2025\bge\real_data_quant\input_data .txt  /data/local/tmp/bge_model//可执行文件
adb push $QNN_SDK_ROOT/bin/aarch64-android/qnn-net-run /data/local/tmp/bge adb push $QNN_SDK_ROOT/lib/aarch64-android/libQnnHtp.so /data/local/tmp/bge
adb push $QNN_SDK_ROOT/lib/aarch64-android/libQnnHtpNetRunExtensions.so /data/local/tmp/bge
adb push $QNN_SDK_ROOT/lib/aarch64-android/libQnnHtpPrepare.so /data/local/tmp/bge
adb push $QNN_SDK_ROOT/lib/hexagon-v68/unsigned/libQnnHtpV68Skel.so /data/local/tmp/bge
adb push $QNN_SDK_ROOT/lib/aarch64-android/libQnnHtpV68Stub.so /data/local/tmp/bge
#系统so
adb push $QNN_SDK_ROOT/lib/aarch64-android/libQnnSystem.so /data/local/tmp/bge

修改/data/local/tmp/bge_model权限,然后设置环境变量,进行使用bin文件模型的运行。

chmod -R 777 /data/local/tmp/bge_model/*export LD_LIBRARY_PATH=/data/local/tmp/bge_model/:/vendor/lib64/:$LD_LIBRARY_PATH
export ADSP_LIBRARY_PATH=/data/local/tmp/bge_model./qnn-net-run --backend ./libQnnHtp.so --retrieve_context  ./bge_8295_qnn.serialized.bin --input_list ./input_data.txt  --output_dir output2

 使用so 模型文件进行运行

./qnn-net-run --backend ./libQnnHtp.so --model  ./bge_8295_qnn.so --input_list ./input_data.txt --output_dir output1

如果是int 型的输入数据,强烈建议加上--use_native_input_files. 表示读输入文件的时候按照int32读而不是float32,精度会大大改善。

相关文章:

  • Shell脚本-嵌套循环应用案例
  • 塔能科技:点亮节能之光,赋能工厂与城市
  • 013几何数学——算法备赛
  • 科技助力防灾减灾:卫星电话走进应急救援队伍
  • Python创意爱心代码分享指南
  • ​LangChain、LlamaIndex、MCP、Spring AI、Ollama​ 和 ​DeepSeek​ 的定义、关系及典型架构设计
  • 完美解决.NET Framework 4.0 中 System.Drawing 库不支持 WebP 格式的图像处理
  • Docker 获取 Python 镜像操作指南
  • Dots:动态实现GPUECSAnimationBaker的受击变红效果
  • 不同参数大小的DeepSeekR1模型对Java中new FileInputStream(“test.txt“).seek(100);语法错误的检查
  • WPF之Button控件详解
  • Golang|外观模式和具体逻辑
  • 【杂谈】-人工智能驱动的网络安全威胁:新一代网络钓鱼
  • 第33周JavaSpringCloud微服务 分布式综合应用
  • 系统架构师2025年论文《论面向对象的软件设计——UML 在面向对象软件架构中的应用》
  • GpuGeek全面接入智谱GLM Z1系列推理模型!!
  • VLM-E2E:通过多模态驾驶员注意融合增强端到端自动驾驶——论文阅读
  • 解决redis序列号和反序列化问题
  • 喷泉码解码成功率
  • Transformer数学推导——Q29 推导语音识别中流式注意力(Streaming Attention)的延迟约束优化
  • 餐饮店直播顾客用餐,律师:公共场所并非无隐私,需对方同意
  • 促进产销对接,安徽六安特色产品将来沪推介
  • 劳动最光荣!2426人受到表彰
  • 传智教育连续3个交易日跌停:去年净利润由盈转亏
  • 人民日报社论:做新时代挺膺担当的奋斗者
  • 【社论】用生态环境法典守护生态文明