高通跃龙IQ-9100平台上部署7B模型FastRPC SMMU限制突破记录(2): Python API突破与运行时内存机制
📌 上一篇回顾
在上篇中,我们发现本地编译的 7B 模型二进制体积(8.4 GB)是云编译(4.7 GB)的两倍,根本原因是多图编译模式下 CLI 工具无法启用weights_packing,且 JSON 配置被静默忽略。
本篇 将展示如何通过 QAIRT Python API 突破这一限制,将二进制压缩到 4.7 GB。但压缩后仍面临 4 GB IOVA 上限的质疑——为什么 4.7 GB 模型能成功运行?答案藏在三个运行时Runtime机制中。
【接上一篇】
1. 突破口:QAIRT Python API
在 CLI 工具走不通的情况下,转向 QAIRT SDK 提供的 Python API。
关键发现:Python API 的 NativeExecutor 直接调用 QNN C API,绕过了 CLI 的 JSON parser。 所有在 CLI 上被静默忽略的配置项,Python API 都能正确传递。
核心代码:
import qairt
from qairt.api.compiler.config import CompileConfig
from qairt.api.common.backends.http.config import HtpGraphConfig, HtpContextConfig
# 1. 转换 ONNX → DLC(内存中的中间表示)
prompt_model = qairt.convert(prompt_onnx, encodings=prompt_enc, float_bitwidth=16)
token_model = qairt.convert(token_onnx, encodings=token_enc, float_bitwidth=16)
# 2. 编译 DLC → context binary,启用 weights_packing
config = CompileConfig(
backend="HTP",
graph_custom_configs=[
HtpGraphConfig(name=prompt_name, optimization_type=3, weights_packing=True),
HtpGraphConfig(name=token_name, optimization_type=3, weights_packing=True),
],
context_custom_configs=[HtpContextConfig(weight_sharing_enabled=True)],
)
result = qairt.compile([prompt_model, token_model], config=config)
result.save(output_bin)
这里有几个要点:
weights_packing=True是关键——直接通过 Python 对象传给 QNN C APIoptimization_type=3对应最高优化级别(在 CLI 中通过--config_file的 0 键设置,但同样被静默忽略)weight_sharing_enabled=True使 prompt 和 token 图共享权重,避免重复映射float_bitwidth=16确保浮点回退使用 fp16 而非 fp32
A/B 测试:显示模型 part 2 中的那一层是smmu 的限制最大,导致失败的关键, 其中Part 2的测试结果如下:
| 方法 | Part 2 大小 | 缩减 |
|---|---|---|
CLI qnn-context-binary-generator(默认) |
672.6 MB | — |
CLI + weights_packing |
672.6 MB | 0%(被忽略) |
Python API + weights_packing=True |
337.3 MB | 49.9% |
2. 11-split 的尝试
首先用 11-split 打包模型(4.7 GB 总量)部署测试。解决了图名称排序问题(10/11 → T0/T1)后,Genie 能正确加载前 7 个 context,但 context 8、9、10 映射失败:
fastrpc memory map for fd: 117 with length: 352321536 failed with error: 0x1
Failed to map shared weights for contextId 8
模型能运行但输出乱码——后 3 个 Transformer 层和 LMHead 的权重缺失了。
11 份太多了。
3. 6-split 的逆转
关键转折来自云编译的 6-split 模型——同样 4.7 GB,但成功部署了。
测试结果:
| 测试 Prompt | 结果 |
|---|---|
| 英文数学 “What is 2+3” | “2 + 3 equals 5.” ✓ |
| 中文解释 “请用中文解释什么是量子计算” | 正确、连贯的中文回答 ✓ |
推理输出完全正确。中英文均验证通过。
4. 为什么 4.7 GB 能在 4 GB 限制下工作?
答案在三个运行时机制中:
4.1. use-mmap: true —— 按需分页加载
"QnnHtp": {
"use-mmap": true,
"mmap-budget": 0
}
权重通过 memory-mapped file 加载,操作系统使用 on-demand page faulting——只有当前推理实际访问的页面才会被映射。4.7 GB 的权重不需要同时全部驻留在 IOVA 空间里。
4.2. weight_sharing_enabled: true —— 权重共享
"context": {
"weight_sharing_enabled": true
}
每个 part 包含两个图(prompt + token),但它们共享同一份权重。没有权重共享的话,实际映射量翻倍;有了它,每份权重只需映射一次。
4.3. 非致命的映射失败 —— 运行时优雅降级
内核日志(dmesg)中仍然存在 failed to map buffer 警告:
qcom,fastrpc-cb ...: compute-cb@2: failed to map buffer, fd = 25
qcom,fastrpc-cb ...: compute-cb@3: failed to map buffer, fd = 35
但这些是非致命的。Genie 运行时优雅地处理了这些失败——可能是辅助缓冲区(profiling、secondary cache、或冗余映射)映射失败,但不影响核心推理路径。
应用层分配的显式缓冲区只有 155 MB(12 个 buffer),远低于 4 GB 限制:
[INFO] "Allocated total size = 162038272 across 12 buffers"
4.4 与 4B 模型的对比
| 指标 | Qwen3-4B (w4a16) | Qwen2.5-7B (混合精度) |
|---|---|---|
| 总模型大小 | 3.0 GB (4 parts) | 4.7 GB (6 parts) |
| 显式分配缓冲区 | 328 MB / 8 buffers | 155 MB / 12 buffers |
| 内核映射失败 | 3-4 个非致命 | 4-5 个非致命 |
| 推理质量 | 正确 ✓ | 正确 ✓ |
7B 模型的显式缓冲区分配(155 MB)反而比 4B 模型(328 MB)还少——说明 Genie 运行时对 7B 模型做了更积极的内存优化。
小结
本篇中,我们通过 Python API 成功启用了 weights_packing,将二进制从 8.4 GB 压缩到 4.7 GB,并发现 6-split 可以成功部署而 11-split 失败。更重要的是,我们揭示了三个运行时机制(use-mmap、weight_sharing、非致命映射失败)如何让 4.7 GB 模型在 4 GB IOVA 限制下正常工作。
下一篇预告:
部署过程中我们还遇到了图名称排序导致的张量找不到问题,以及soc_model 配置错误引发的乱码输出。我们将在下一篇中解决这些细节,并给出完整的本地编译流程(约 16 分钟)、三层方案总结、假设验证,以及最重要的经验:理论内存限制 ≠ 实际限制。
最终,我们将回答那个核心问题——这是软件限制,不是硬件限制。
更多推荐



所有评论(0)