从 LLaMA-Factory 微调到高通 NPU 部署: Qwen-0.6B 全链路移植指南
前言
在大模型端侧化部署的趋势下,如何将微调后的 LLM 跑在手机 NPU 上是很多开发者的痛点。本文将手把手教你如何将使用 LLaMA-Factory 微调后的 Qwen-0.6B 模型,一步步移植到高通(Qualcomm)骁龙平台的 NPU 上,实现低功耗、高速度的本地化推理。
一、 导出微调模型
首先,在 LLaMA-Factory 界面中选择好微调后的检查点(Checkpoint),填写导出路径,点击 “开始导出” 。
![[图片]](https://qiniu.meowparty.cn/coder.2023/2026-03-22/78f4c7e97533493996c71909f3b0a776.png)
导出成功后,你会在目录下看到如下文件:
model.safetensors(模型权重)config.json(模型配置)tokenizer.json等(分词器相关)
要将微调后的 Qwen-0.6B 模型移植到高通 NPU,第一步就是格式转换。safetensors 是目前 Hugging Face 推崇的安全权重格式,而 ONNX 则是进入高通工具链(QNN/SNPE)的通用门票。
以下是详细的操作步骤:
二、 格式转换:从 Safetensors 到 ONNX
1. 转换为 PyTorch 权重
由于部分旧版转换工具不支持 safetensors,建议先将其转回标准的 pytorch_model.bin 。
import torch from safetensors.torch import load_file # 1. 路径设置 safetensors_path ="./qwen0_6b/model.safetensors" pytorch_bin_path ="./qwen0_6b/pytorch_model.bin"# 2. 加载并保存 weights = load_file(safetensors_path) torch.save(weights, pytorch_bin_path)[cite_start]print(f"转换成功:{pytorch_bin_path}")[cite:42,43,44,46,51,54]注意:转换后,请确保你的 config.json 中的 architectures 字段正确(对于 Qwen0.6B 通常是 Qwen2ForCausalLM)。
2. 使用 Optimum 导出 ONNX
导出 LLM 涉及复杂的 KV Cache 处理,强烈建议使用 Hugging Face 的 Optimum 库 。
安装工具:
[cite_start]pip install optimum[exporters] onnx onnxruntime [cite: 62]执行导出:
针对 NPU 部署,必须开启 with past 模式以保证推理速度 。
optimum-cli export onnx \ --model ./qwen0_6b \ --task text-generation-with-past \ --trust-remote-code \[cite_start]./qwen_onnx_out/ [cite: 67, 69, 71, 73, 75]输出结果:你会得到 decoder_model.onnx 和 decoder_with_past_model.onnx。
注意:--task text-generation-with-past:这非常关键!这会生成两个模型,一个处理初始 Prompt,另一个利用 KV Cache 负责后续 Token 生成 。三、 高通 NPU 关键优化(必看!)
高通 Hexagon NPU 对算子有特定要求,进入工具链前需完成以下优化:
- Opset 版本: 建议使用 Opset 17 或更高版本 。如果 optimum 默认导出较低,可以指定:
--opset 17- 静态形状(Static Shapes): NPU 在静态形状下性能最强。建议将输入固定,如
batch_size=1, sequence_length=512。 - 模型简化: 使用
onnxsim消除冗余算子,降低报错率 。
pip install onnxsim onnxsim ./qwen_onnx_out/decoder_model.onnx ./qwen_onnx_out/decoder_model_sim.onnx 四、验证 ONNX 模型
在交给高通工具链之前,先确保 ONNX 模型是正确的:
import onnx import onnxruntime as ort model = onnx.load("./qwen_onnx_out/decoder_model.onnx") onnx.checker.check_model(model) print("ONNX 模型校验通过!")五、 高通工具链(QNN)模型编译
1. 环境准备
确保你的 Linux 开发机已安装:
- Qualcomm AI Engine Direct SDK (QNN)
- Android NDK (建议 r25c 或 r26)
- 设置环境变量:
exportQNN_SDK_ROOT=/path/to/qnn_sdk exportANDROID_NDK_ROOT=/path/to/android_ndk source$QNN_SDK_ROOT/bin/envsetup.sh 一旦你拿到了 decoder_model.onnx,你接下来的操作流程是:
- 准备量化数据:从你的微调数据集中抽取 100 条样本,用于高通工具的 Post-Training Quantization (PTQ)。
- 使用 QNN Converter:
qnn-onnx-converter -i decoder_model.onnx -o qwen_qnn.cpp --input_list_file calibration_data.txt 要把微调后的 Qwen-0.6B 最终跑在手机 NPU 上,你需要经历交叉编译的过程。高通 QNN SDK 提供了专门的工具,将转换后的中间代码(.cpp / .bin)编译成手机端可执行的二进制文件。
在高通架构中,通常涉及两种文件:
- .so (Model Library):模型的结构描述库。
- .bin (Context Binary):针对特定 NPU 硬件优化并序列化后的图模型(这是性能最高的形态)。
2. 生成模型动态库 (.so)
使用 qnn-onnx-converter 将 ONNX 转为 C++ 代码,再通过 qnn-model-lib-generator 编译 。
qnn-model-lib-generator \ -c qwen_model.cpp \ -b qwen_model.bin \ -o ./model_libs \ -t aarch64-android # 指定目标平台为 Android ARM64- 输出结果:在
./model_libs/aarch64-android/目录下,你会得到一个libqwen_model.so。 - 用途:这个文件包含了模型的拓扑结构,可以被高通的推理引擎加载。
3. 生成上下文二进制文件 (.bin) —— 性能核心
为了实现“秒开”和极致加速,必须生成针对 HTP(Hexagon Tensor Processor)优化的 Context Binary 。
qnn-context-binary-generator \ --model ./model_libs/aarch64-android/libqwen_model.so \ --backend libQnnHtp.so \ --output_dir ./context_out \ --binary_file qwen_htp_context --backend libQnnHtp.so:这步至关重要,它指定使用 HTP (Hexagon Tensor Processor) 后端,即真正的 NPU 加速。- 输出结果:
qwen_htp_context.bin。
注意:此步骤通常建议在连接了真机的情况下运行(通过 adb),或者使用高通提供的模拟器,因为生成 context 需要针对具体的芯片架构(如 v73, v75)。
六、 手机端集成与验证
在 Android 项目中,通过 C++/JNI 调用 QNN API 加载 qwen_htp_context.bin 即可执行推理 。
现在你手里有了:
- 模型资产:qwen_htp_context.bin。
- 推理引擎库:从 QNN SDK 中提取的 libQnnHtp.so, libQnnSystem.so 等。
快速验证:
在写 App 前,先用 qnn-net-run 工具在手机 shell 中测试 :
[cite_start]./qnn-net-run --container qwen_htp_context.bin --backend libQnnHtp.so --input_list input_data.txt [cite: 198, 199, 200]若能正常输出 Tensor 结果,说明模型已成功跑在 NPU 上!
推理代码核心逻辑(C++/JNI):
在 Android 的 C++ 层,你需要调用 QNN API 来加载这个 .bin 文件:
// 1. 初始化 QNN 实例 Qnn_BackendHandle_t backendHandle; QnnBackend_initialize(..., &backendHandle); // 2. 加载之前生成的 Context Binary Qnn_ContextHandle_t contextHandle; // 通过读取 qwen_htp_context.bin 的 buffer 传入 QnnContext_createFromBinary(backendHandle, deviceHandle, ..., binaryBuffer, binarySize, &contextHandle, ...); // 3. 准备 Tensor 数据并执行推理 QnnGraph_execute(graphHandle, inputTensors, numInputs, outputTensors, numOutputs, ...); 🛠 避坑总结
- 版本一致性: 编译
.so的 NDK 版本必须与 Android 项目一致 。 - 内存溢出: 若 Context Length 设得太大,NPU 内存会溢出,建议从 512 或 1024 测起 。
- 算子支持: 若生成 Binary 报错,需回到 ONNX 阶段进行算子融合或替换 。
希望这篇指南能帮你顺利把大模型装进兜里!如果有问题,欢迎在评论区交流。
如果你觉得有用,欢迎点赞、收藏、关注!