高通跃龙IQ-9075平台的开发记录(1): 边缘农业AI助手的端到端部署
设备: 高通跃龙IQ-9075 EVK(SA8775P,Hexagon v73,16 GB)
系统: Ubuntu 24.04.4 LTS,内核 6.8.0-1071-qcom,QNN 2.43,Genie 1.14.0
模型: Qwen2.5-7B-Instruct,w8a16,6-split,约 4.7 GB
代码: ThunderSoft-XA/Edge-AI-agriculture-assistant
这是一次把「端侧 7B 咨询 + 温湿度传感器 + Web 告警」串起来的 demo。板子上离线推理,浏览器里能看实时读数、能对话,超温超湿时会主动推建议。下面按做过的顺序写:模型怎么编、板怎么备、传感器怎么接、应用怎么上、知识库怎么选。步骤写清,代码不全贴。
前言:硬件平台与目标
本项目使用的是高通跃龙IQ-9075开发板,
做成什么样
设备上常驻两个进程:
| 服务 | 端口 | 作用 |
|---|---|---|
genie-server | 8081 | C++ HTTP 包一层 Genie,dlopen libGenie.so,模型常驻 HTP |
| FastAPI + Uvicorn | 8080 | Modbus 轮询、拼接ChatML、FTS5 检索、SSE 聊天与告警推送 |
数据流大致是:
浏览器 ──HTTP/SSE──▶ FastAPI (:8080) ├─▶ genie-server (:8081) ──▶ Hexagon HTP / Qwen2.5-7B ├─▶ pymodbus TCP ──▶ 仁科温湿度 (192.168.2.100:502) └─▶ SQLite(读数 / 告警 / 对话 / FTS5 知识库)为什么拆成两进程:Genie 要长时间占住 HTP 与数 GB 权重;业务侧要做轮询、检索、Web。HTTP 隔开后,模型加载只发生在推理进程启动时,聊天请求不必每次冷启动。
网络拆成两路:WiFi(
ubuntu.local)管 SSH 和 Web;以太网口直连传感器(板子192.168.2.1/24,传感器192.168.2.100)。管理面和传感面不共用同一二层网段,避免传感器静态地址和办公 DHCP 抢地址。
仓库:Edge-AI-agriculture-assistant。
应用说明在app/README.md,推理服务在app/inference-service/。
模型准备走高通官方 LLM on Genie 教程,本文按实际编译过程记录关键步骤和参数。
板端实测量级(Genie 1.14.0 + QNN 2.43):常驻加载约 2.2 s,TTFT 约 176 ms,约 8.3 tok/s,单次回复常见 7–37 s。
一、模型准备
目标产物:6 个 context binary +genie_config.json+tokenizer.json,合计约 4.7 GB,放到板上/opt/qc-ai/models/qwen2.5-7b-local-6split/。
流水线与高通 LLM on Genie 一致:预量化 ONNX → 拆分 → DLC → HTP context binary → Genie 加载。
拆成 6 份,是因为整模一次编进 HTP 上下文太大,拆分后每段图单独编译、运行时再串起来;prompt 图与 token 图还可共享权重,省一份拷贝。
1.1 主机环境
| 用途 | 环境 |
|---|---|
| 云编译 | Python 3.11 venv,qai_hub_models,AI Hub API token |
| 本地编译 | Python 3.10 venv,QAIRT SDK 2.42+,内存建议 40 GB+ |
QAIRT 自带的.pyd扩展是按 Python 3.10 ABI 编的,3.11 装不上,所以本地编译必须另开 3.10 环境。云编译走 AI Hub 的 Python 包,用 3.11 即可。
克隆并检出测过的ai-hub-models版本;云路径安装带qwen2_5_7b_instruct的 extra;本地再装 onnx / transformers 等。
云编译前:
python-m qai_hub configure--api_token <YOUR_TOKEN>本地编译:
$env:QNN_SDK_ROOT ="C:\Qualcomm\AIStack\QAIRT\2.42.0.251225"1.2 下载预量化包
高通提供约 14.9 GB 的预量化 ONNX zip,里面已有 AIMET encodings。放到qai-hub-models约定的 cache 目录后,导出脚本会自己读。可用curl -C -断点续传。
预量化在主机侧完成权重量化与校准信息;后面编译主要是把这些张量与图结构变成 HTP 能执行的 context binary,而不是从浮点大模型重新训。
1.3 云编译与本地编译
日常复现优先走云编译:把拆分、上云、回拉交给export,参数与官方设备表对齐,减少因为 SDK 或环境差异造成的坑。
python-m qai_hub_models.models.qwen2_5_7b_instruct.export `--device"Dragonwing IQ-9075 EVK"`--output-dir"<workspace>\output"`--synchronous--device会选中目标 SoC 的编译配置,例如 Hexagon 代数、VTCM、soc 相关选项;编错设备,二进制可能能加载但行为不对。中断后重跑同一条命令即可,已完成的 job 会复用缓存。
本地编译在需要离线、或要改编译开关时用。仓库脚本对应云路径里的三步,只是落在本机 QAIRT 上:
prepare_onnx_6split.py— 拆成 6 份 ONNX + encodingsconvert_6split_to_dlc.py --convert-only— ONNX 转 DLC,DLC 是 QNN 的中间图格式compile_6split_from_dlc.py—qnn-context-binary-generator出 HTP context binary
本地须与云端对齐的关键开关:
| 参数 | 取值 | 说明 |
|---|---|---|
soc_model | 77 | SA8775P。写成 0 时离线编译走通用路径,板上常出现乱码 |
dsp_arch | v73 | 与板子 Hexagon 代数一致 |
weights_packing | True | 把低比特权重打进 binary 内部布局,体积约减半;须走 Python API,CLI 会忽略该键 |
weight_sharing_enabled | True | prompt / token 两套图共用一份权重 |
vtcm_size_in_mb | 8 | 片上 VTCM 配额,和云编译一致 |
| 图名称 | prompt_ar128_cl4096_N_of_M等 | Genie 从图名解析自回归长度ar与上下文长度cl |
soc_model决定编译器按哪颗 SoC 的算子与内存模型出码;图名称则不是给人看的标签,运行时要靠它配置 KV 与序列行为,编错只能重编,二进制事后改名会撞完整性校验。
云、本地在开关对齐时,产物体积应接近:
| 文件 | 约大小 |
|---|---|
part_1_of_6.bin,embedding | 1.0–1.1 GB |
part_2–part_5,decoder | 各约 677 MB |
part_6.bin,LM head | 约 975 MB |
| 合计 | 约 4.7 GB |
genie_config.json常见项:context.size=4096,n-vocab=152064,samplertemp=0.6 / top-k=30 / top-p=0.85,backend.type=QnnHtp。
1.4 模型侧踩过的坑
FastRPC 单缓冲大约 1 GB 的 SMMU 上限。
AP 经 FastRPC 把权重缓冲映射进 DSP 的 SMMU 时,单块连续缓冲大约有 1 GB 的硬限制。嵌入层单独约 1040 MB,刚好超过这个限制,加载时报错fastrpc memory map ... failed,板子物理内存非常充足,小缓冲也能映射成功,就是这一块过不去。早期的做法是把 embedding 改到 CPU 侧用查找表(LUT)处理,这样这块权重就不必整块映射到 DSP。后来打开weights_packing,当前这套 6-split 包在 IQ-9075 上不必再使用 LUT。
图名称写错会乱码。
本地曾编成qwen2_5_7b_instruct_prompt_2_of_8这类名字,模型能加载,输出却是乱码。Genie 会从图名里解析ar(自回归长度)和cl(上下文长度),正确形态类似prompt_ar128_cl4096_2_of_8。二进制事后改名会撞完整性校验,只能按正确格式重编。
soc_model一定要写正确。
如果本地编译模型,x86 主机推断不出板端特性。IQ-9075 / IQ-9100 这一路要显式正确配置soc_model=77,否则输出不对,很难debug。
QAIRT 2.35 的--float_bitwidth 16。
该选项曾错误改写 RmsNorm 里本该保持量化的张量类型,HTP 校验报失败。当时改用--float_bitwidth 32绕过。另外,设备上的 DSP 固件要和编译用的 SDK 版本匹配,不能只升主机这边的编译器。
本地编译体积曾是云端的两倍。
早期本地不用weights_packing时,合计大约 9 GB,云端同配置大约 4.7 GB。差异在 context binary 生成阶段。打开 packing 并用 Python API 编译后,两边体积才对齐。
硬件上也从 Thundercomm Ride / 旧 QCS9100 栈迁到过 Addons IQ-9075 EVK。换板本身不用重写应用,但网络、PPA 里的 QNN 版本、FastRPC 的重试行为要对一下。新板上偶发非致命的failed to map buffer,运行时会换 context bank 再试,不影响输出。
二、板端环境与模型上板
2.1 刷机与基础系统
板子需已刷镜像并完成 Ubuntu 初始化(高通文档:刷机与 Use Ubuntu on IQ9)。SSH:ubuntu@ubuntu.local。同网段 WiFi;Windows 侧要能解析.local,否则直接用 IP。
2.2 安装 QNN / FastRPC
sshubuntu@ubuntu.localsudoaptupdatesudoaptinstall-yqcom-fastrpc1 libqnn1 qnn-toolsls/dev/fastrpc-cdsplibqnn1提供 HTP 后端;FastRPC 设备节点是用户态把缓冲交给 CDSP 的入口。没有/dev/fastrpc-cdsp,后面 Genie 无法把图卸到 DSP。
本 demo 实测:libqnn12.43.0;ADSP 库路径/usr/lib/rfsa/adsp。ADSP_LIBRARY_PATH要指到 skel 等 DSP 侧库,否则 stub 找不到对端。
2.3 安装 Genie
Genie 不在 Ubuntu PPA 里,从主机 QAIRT 的 aarch64 产物拷:
scp"..\QAIRT\..\aarch64-oe-linux-gcc11.2\genie-t2t-run"ubuntu@ubuntu.local:/tmp/ scp"..\QAIRT\..\aarch64-oe-linux-gcc11.2\libGenie.so"ubuntu@ubuntu.local:/tmp/板上:
sudocp/tmp/genie-t2t-run /usr/local/bin/sudocp/tmp/libGenie.so /usr/local/lib/sudoldconfigGenie 负责按genie_config.json加载多份 context binary、管理 KV、做采样;底层仍调 QNN HTP。genie-t2t-run是命令行入口,libGenie.so是同一套运行时。
2.4 推送模型包
ssh ubuntu@ubuntu.local"mkdir -p /opt/qc-ai/models/qwen2.5-7b-local-6split"scp <bundle>\*ubuntu@ubuntu.local:/opt/qc-ai/models/qwen2.5-7b-local-6split/目录内需同时有.bin、genie_config.json、tokenizer.json;配置里的相对路径按该目录解析。
2.5 先用 CLI 验证推理
cd/opt/qc-ai/models/qwen2.5-7b-local-6splitexportADSP_LIBRARY_PATH=/usr/lib/rfsa/adsp genie-t2t-run-cgenie_config.json-p'<|im_start|>system You are a helpful assistant. Do not repeat yourself.<|im_end|> <|im_start|>user What is 2+3?<|im_end|> <|im_start|>assistant '期望看到正常英文或数字答案,而不是乱码。Prompt 必须是 ChatML:模型按该模板微调,缺 system/user/assistant 标记时行为不稳定。system 里固定带Do not repeat yourself:Genie 侧没有好用的 repeat_penalty 时,开放题容易循环复读。
每调一次genie-t2t-run都要重新加载模型,大约十几秒。后面改成常驻genie-server,就是为了把这几十秒从每次请求里拿掉。
三、推理服务 genie-server
FastAPI 不直接起子进程跑genie-t2t-run,而是通过 HTTP 调本机genie-server。模型在进程内只加载一次;请求只做 prompt → token 流。
3.1 编译
main.cpp用dlopen/dlsym解析 Genie,编译期不链接高通头文件与.so,交叉工具链不必安装 QAIRT。在 WSL 或 Linux 上:
cdapp/inference-service ./build.sh--cross# 产物:build/genie-server,aarch64scpbuild/genie-server ubuntu@ubuntu.local:/tmp/sshubuntu@ubuntu.local"sudo cp /tmp/genie-server /usr/local/bin/"也可在板子上原生./build.sh,需 cmake / g++。
3.2 接口与常驻
genie-server-c/opt/qc-ai/models/qwen2.5-7b-local-6split/genie_config.json# 默认 127.0.0.1:8081| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /health | 存活 |
| POST | /generate | body 带完整 ChatMLprompt,SSE 回{"token":"..."},结束[DONE] |
| POST | /reset | 清 KV cache |
HTP 上同一时刻只跑一路生成更稳,所以忙时回 429,由业务侧排队或重试。systemd 单元见app/inference-service/genie-server.service;qc-ai-app.service通过Requires=依赖它,避免应用起来时推理口还没听。
四、传感器选型与接线
4.1 选型
工业上比较常用的是Modbus TCP协议的传感器,这里我们选用了仁科RS-WS-ETH-6J-ModbusTCP:温湿度一体,Modbus TCP,传感器作 TCP Server,端口 502;供电 DC 7–30 V;精度大约温度 ±0.5°C、湿度 ±3%RH。
选 TCP Server,是因为板子作 Client 定时去读即可,传感器侧不用知道板子 IP;寄存器只有湿度和温度两个,应用映射简单。
| 地址 | 内容 |
|---|---|
| 0 | 湿度 %RH,原始值 ÷10 |
| 1 | 温度 °C,原始值 ÷10 |
例如原始562→ 56.2%RH,236→ 23.6°C。温度大于 32767 按有符号补码处理。
4.2 Windows 上首次配网
出厂 IP 不一定落在要用的网段,须用官方RS-ModBusTCP-Config写一次静态参数:
- 传感器网线接 Windows PC,本机网卡设成同网段,例如
192.168.2.10/24,上电。 - 打开配置工具 →「搜索」→ 双击设备。
- 网络参数:工作模式
TCP Server;端口502;静态 IP192.168.2.100,掩码255.255.255.0,网关192.168.2.1;IP 获取 StaticIP;清掉多余 server 项。
4.「参数配置」保存;有「设备参数」页再读一次、配一次。 - 断电重启传感器。搜不到时先关 Windows 防火墙,并确认 PC 与传感器同网段。
部分参数写在传感器非易失配置里,不掉电重启可能仍跑旧 IP 或旧模式。
4.3 接到 IQ-9075
板子连接传感器会占用有线网卡,我们先用无线网络连接开发机和板子。
- 传感器从 PC 改接到板子 ETH,板子的以太网口是
end0。 - 板上建静态以太网,本 demo 用 NetworkManager,连接名
sensor-net:
sudonmcli connectionaddtypeethernet ifname end0 con-name sensor-net\ipv4.method manual\ipv4.addresses192.168.2.1/24\connection.autoconnectyes板子与传感器必须落在同一子网/24,且板子不要跟传感器抢192.168.2.100。
- 连通性:
ping-c3192.168.2.100timeout2bash-c'echo > /dev/tcp/192.168.2.100/502'&&echoOPEN||echoCLOSED应用默认:SENSOR_HOST=192.168.2.100,SENSOR_PORT=502,SENSOR_POLL_INTERVAL=5秒,pymodbus异步轮询。轮询结果进 SQLite,供仪表盘、告警阈值和拼进 LLM 的「当前读数」共用同一数据源。实机曾读到约23.6°C / 56.2%RH。
没有硬件时,可用app/tests/mock_sensor.py与mock_sensor_extreme.py,systemd drop-in 把SENSOR_HOST/PORT指到 mock。
五、应用部署
5.1 目录与依赖
应用源码在仓库app/:services/(sensor / llm / alert / database / event_bus)、routers/、static/、data/knowledge/、deploy/。
一键部署,Git Bash 或 WSL:
cdapp/deploy ./deploy.sh ubuntu@ubuntu.local脚本会建/opt/qc-ai/app/、SCP 文件、pip装依赖、装 systemd 并启动。
手动时:建目录并chown→ SCPmain.py、config.py、services/、routers/、static/、data/、deploy/→ 板上:
cd/opt/qc-ai/app pip3install--break-system-packages-rrequirements.txtsudocpdeploy/qc-ai-app.service /etc/systemd/system/sudosystemctl daemon-reloadsudosystemctlenable--nowqc-ai-app--break-system-packages是 Ubuntu 3.12 对系统 Python 的 pip 限制下的写法;包版本见app/README.md。
5.2 配置
均在config.py,用 systemd drop-in 覆盖即可,不必改代码。常用项:
| 变量 | 默认 | 含义 |
|---|---|---|
SENSOR_HOST/PORT/SLAVE_ID | 192.168.2.100/502/1 | Modbus |
SENSOR_POLL_INTERVAL | 5.0 | 轮询秒 |
LLM_INFERENCE_URL | http://127.0.0.1:8081 | genie-server |
LLM_TIMEOUT | 120 | 推理超时秒 |
ALERT_EXTREME_HEAT | 40.0 | 极端高温,Critical,会调 LLM 出建议 |
ALERT_FROST_WARNING | 0.0 | 霜冻,Critical |
ALERT_HIGH_TEMP/LOW_TEMP | 35/5 | Warning,记日志 |
ALERT_HIGH_HUMIDITY/LOW_HUMIDITY | 95/20 | Warning |
ALERT_COOLDOWN_SECONDS | 300 | 同类告警去重间隔 |
DB_PATH | /opt/qc-ai/app/data/qc-ai.db | SQLite |
KB_ENABLED | true | 是否注入知识库 |
KB_TOP_K | 3 | 注入段落数 |
KB_MAX_CHARS_PER_PASSAGE | 500 | 单段截断 |
Critical 与 Warning 分开:只有极端温湿度才再跑一轮 LLM 生成防护建议,避免阈值抖动时把 HTP 打满。ALERT_COOLDOWN_SECONDS用来在冷却期内合并同类告警。
5.3 前端与 API
浏览器打开http://ubuntu.local:8080/:仪表盘、流式对话、告警条。前端无构建步骤,静态文件由 FastAPI 直接送。
| 接口 | 作用 |
|---|---|
POST /api/chat | 咨询,SSE 流式 |
GET /api/sensors/current、/history | 当前与历史读数 |
GET /api/alerts | 告警历史 |
GET /api/events | 传感器、告警等实时推送 |
对话路径:取当前读数 → 可选 FTS5 检索 → 拼 ChatML →genie-server→ SSE 回浏览器。Critical 告警走并行路径,同样经 SSE 推到页面,用户不用先发问。
六、端侧知识检索
6.1 选型
约束:推理时 HTP 打满;上下文 4096;检索不能拖成秒级。考虑过几种做法:
- A. SQLite FTS5 / BM25:关键词倒排,Top-K 塞进 prompt。不加额外神经网络,毫秒级,可离线;同义词靠词典补齐。
- B. 在线 API:拉天气、农情。要外网,延迟受外网影响。
- C. A+B:本地静态知识加在线动态。信息更全,但本 demo 未接外网。
- D. 本地 embedding + 向量库:在 ARM CPU 上编向量,语义匹配更好,但对这份 FAQ 来说太重。
这里只采用方案 A。FTS5 建倒排索引,BM25 按词稀有度与词频打分;农业术语相对固定,关键词命中率够用。「番茄 / 西红柿」要对上,就把同义写进用户词典,而不是先上向量库。
检索通常不到 5 ms。Token 预算:system + 传感器 + 2–3 段知识约 800–1500 token + 用户问句,留给生成大约两千 token。
6.2 接入方式
首次启动把data/knowledge/*.txt导入 SQLite,建 FTS5 虚表。用 external content 模式时,索引不复制一份全文,省空间。对话前search_knowledge,命中段落进 system:
Current readings: Temperature {temp}C, Humidity {humidity}% Relevant knowledge: {retrieved_passages} ... Do not repeat yourself.原理上这就是检索增强:模型参数不改,只在上下文里塞可引用的条文,回答更贴本地农技材料;塞太多会挤占生成长度,所以要KB_TOP_K与单段截断。
知识文件命名:{category}_{标题}.txt,例如tomato_高温防护措施.txt。段落约 300–400 字,在句号处切,可留几十字符 overlap,避免知识点卡在边界上丢半句。PDF/Word 在开发机先转明文再上板。
KB_*可关检索或改 Top-K。知识文本的免责声明见app/data/knowledge/DISCLAIMER.md。
七、端到端确认
模型、Genie、传感器、应用都起来后,可以按下面几项快速确认链路是否跑通。
推理curl http://127.0.0.1:8081/health;或再跑一次带 ChatML 的短问,看中英文是否正常、会不会循环复读。
传感器curl http://127.0.0.1:8080/api/sensors/current,温湿度与寄存器换算一致即可。
Web
打开http://ubuntu.local:8080/,仪表盘有数,聊天框能流式出字。
告警
用mock_sensor_extreme或临时改阈值,看 Critical 会不会推告警并带建议,Warning 是否进历史。
知识库
问一句知识库里有的作物问题,看回复是否用到检索段落;也可在 prompt 组装处对一下。
sudosystemctl status genie-server qc-ai-appsudojournalctl-uqc-ai-app-f八、性能对照
| 指标 | 子进程genie-t2t-run | 常驻genie-server |
|---|---|---|
| 模型加载 | 每次请求约 15–20 s | 启动一次约 2.2 s |
| TTFT | 含加载约 20 s | 约 176 ms |
| 吞吐 | 约 8.3 tok/s | 约 8.3 tok/s |
| 内存 | 每请求 fork | 常驻,RSS 峰值约 1.1 GB |
吞吐差不多,差在是否把加载摊到启动期。应用层单次回复时间主要跟生成长度走,常见 7–37 s。
九、扩展和完善方向
本项目是可跑通的边缘农业咨询 demo,不是成品产线系统。已经覆盖:模型上板、常驻推理、真机温湿度、Web 咨询、阈值告警、本地 FTS5 知识注入。
若往产品方向靠,比较自然的扩展包括:知识库加领域词表或第二期接天气 API、告警按作物季节调阈值、多传感器与读数导出等。这些是 demo 之上的完善方向。
参考
- 仓库:https://github.com/ThunderSoft-XA/Edge-AI-agriculture-assistant
- 应用与传感器:仓库文档
app/README.md - 推理服务:仓库文档
app/inference-service/README.md - 高通教程:LLM on Genie
- 设备列表:AI Hub devices
上面记录的是实际搭通这条链路时用到的步骤和结论;SMMU、soc_model、packing、图名称等若还要往下挖,可以对照高通官方文档和设备侧日志继续调查。
