尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

高通跃龙IQ-9100工业平台的开发经验分享(2): 7B模型推理乱码的根因追踪

高通跃龙IQ-9100工业平台的开发经验分享(2): 7B模型推理乱码的根因追踪 设备: 高通跃龙IQ-9100 EVK (SA8775P, Hexagon v73, 双 CDSP)模型: Qwen2.5-7B-Instruct (w4a16 混合精度量化28 层 Transformer)SDK: QAIRT 2.42 (本地编译) Genie 1.14.0 推理运行时日期: 2026-07-23一个缺失的编译参数导致了数周的调试——排除了 9 个错误假设之后才找到真正的根因。背景我们正在把 Qwen2.5-7B-Instruct 大语言模型部署到高通 QCS9100 边缘设备上用 Hexagon DSP (HTP) 加速推理。模型已经通过 Qualcomm AI Hub 云编译成功跑通——推理输出正确、连贯。现在要做的是本地编译用 QAIRT SDK 在 Windows 上直接把模型编译成 HTP 上下文二进制context binary不再依赖云端。本地编译的产物加载到设备上、成功执行——没有任何报错。但输出是乱码。一、问题现象Q: What is 23? A: The value of 2 3 is $\boxed{5}$. oreferalitiesmallalitiesmall...第一个 token 看起来是对的“The value of 2 3 is 5”然后迅速退化为无意义的重复字符。中文同样Q: 请用中文解释什么是量子计算 A: 量子计算是一种利用量子力学原理进行... eferalitiesmall...开头几个 token 基本正确随后完全走偏。关键特征模型加载成功没有任何错误日志第一个 token 往往正确后续 token 迅速发散为乱码相同的设备、相同的 Genie 运行时云编译版本输出完全正确这不是完全不能跑的问题而是看起来在跑但输出是错的——往往比加载失败更难排查。二、9 次失败的假设在找到真正的根因之前我们经历了 9 次尝试和排除。2.1 假设 111-split 结构不对 → 改 6-split本地编译最初用的是 11-split每份 3 层 Transformer云编译用的是 6-split每份 6 层。拆分方式不同可能导致图结构不兼容。执行按云编译的拆分策略重新导出 6-split ONNX重新编译。结果之前出现的usesCrossAttention结构错误消失了但乱码依旧。收获拆分方式确实需要和云编译一致6-split但这不是乱码的根因。2.2 假设 2QAIRT 版本混用 → 统一 2.42之前用 QAIRT 2.40 做 converter、2.42 做 compiler。版本混用可能引入兼容性问题。执行统一使用 QAIRT 2.42 做 converter compiler。结果DLC 文件大小与 2.40 converter 完全一致乱码依旧。2.3 假设 3n-vocab 配置错误 → 改 131072genie_config.json中n-vocab设为 152064Qwen2.5 的实际词汇表大小而云编译版本使用的是 131072。执行改n-vocab为 131072。结果模型内部始终输出 152064 个 logitsn-vocab只影响 tokenizer 的解码范围对推理质量无影响。乱码依旧。2.4 假设 4Gather 编码被移除 → 手动恢复split_onnx函数在拆分时会故意移除 Gather嵌入层的激活编码因 AISW-152612 bug这可能影响量化精度。执行手动在 Part 1 的.encodings文件中恢复嵌入层的编码。结果DLC 大小不变乱码依旧。2.5 假设 5浮点回退没启用 → 用 qairt-quantizer也许需要显式启用--enable_float_fallback让不支持量化的操作回退到浮点计算。执行对所有 12 个 DLC 运行qairt-quantizer --enable_float_fallback。结果工具提示Float Fallback is already applied to the provided DLC——converter 在转换阶段已经自动处理了。乱码依旧。2.6 假设 6VTCM 大小不对 → 4 MB 改 8 MB通过对比发现云编译使用 8 MB VTCMVector Tightly-Coupled Memory本地默认是 4 MB。VTCM 是 Hexagon DSP 上的高速缓存大小可能影响计算精度。执行在HtpGraphConfig中设置vtcm_size_in_mb8重新编译。结果二进制大小从 4703 MB 微调到 4710 MB更接近云的 4720 MB但乱码依旧。收获VTCM 大小确实影响编译产物但 7 MB 的差异背后还有更大的原因。2.7 假设 7AI Hub 云编译有特殊参数 → 尝试远程编译也许云编译管线有一些本地无法复制的特殊步骤执行尝试通过 AI Hub Python SDK 提交远程编译任务。结果上传速度约 200 KB/s12 GB ONNX 文件需要约 17 小时上传。放弃。2.8 假设 8云编译的量化参数是关键 → 分析源码AI Hub 云编译管线硬编码了三个额外参数other_compile_options --quantize_full_type w8a16 --quantize_io--quantize_full_type w8a16会将未量化的嵌入层和 LM Head 从 fp16 进一步量化为 w8a16。这些参数只在云 API 中可用本地 SDK 没有对应选项。分析 AIMET 编码文件后发现所有 Transformer 层的主要权重int4 ✓RMSNorm gammaint16 ✓I/O 激活int16 ✓未编码的初始化器仅 ~0.28 MB0.28 MB 的未编码权重不可能导致全模型乱码。这条路也走不通。2.9 假设 9二进制大小差异 → 反向追踪到这一步我们换了思路——不再猜测原因而是回到最基本的可观测指标。编译方式总二进制大小云编译~4720 MB本地soc_model0, vtcm4MB~4703 MB本地soc_model0, vtcm8MB~4710 MB4703 → 4710 → 4720。差距在缩小但始终没有完全对齐。17 MB 的差异4703 vs 4720对于一个 4.7 GB 的模型来说只有 0.36%——看似微不足道。但在前面的 SMMU 调试经验中我们学到一个规则二进制大小是编译正确性的可靠代理指标。当本地和云的大小完全匹配时输出质量才会匹配。17 MB 的差距说明编译器做了不同的事情。问题是什么导致了不同三、突破soc_model原因在CompileConfig的一个容易被忽略的参数上。QAIRT Python API 的编译配置中有一个HtpDeviceConfig用于指定目标设备信息fromqairt.api.common.backends.htp.configimportHtpDeviceConfig HtpDeviceConfig(soc_model77,# SA8775P (QCS9100)dsp_archv73,# Hexagon v73)我们之前的编译配置中没有设置device_custom_configs。查看 SDK 文档发现当soc_model未指定时默认为0generic。soc_model0意味着编译器不知道目标芯片是什么只能使用通用优化策略。而soc_model77告诉编译器目标是 SA8775P——QCS9100 的底层 SoC。加上这一行重新编译configCompileConfig(backendHTP,graph_custom_configs[HtpGraphConfig(nameprompt_name,optimization_type3,weights_packingTrue,vtcm_size_in_mb8),HtpGraphConfig(nametoken_name,optimization_type3,weights_packingTrue,vtcm_size_in_mb8),],context_custom_configs[HtpContextConfig(weight_sharing_enabledTrue)],device_custom_configs[HtpDeviceConfig(soc_model77,dsp_archv73,cores[HtpDeviceCoreConfig(perf_profilePerfProfile.BURST,rpc_control_latency100)],)],)编译完成。二进制总大小4720.7 MB。与云编译的 4720 MB精确匹配。部署到设备上Q: What is 23? A: 2 3 equals 5. Q: 请用中文解释什么是量子计算 A: 量子计算是一种利用量子力学原理进行计算的技术...正确、连贯的中文回答乱码消失。中英文推理全部正确。四、根因分析4.1soc_model0到底做了什么当编译器不知道目标 SoC 时它无法针对具体硬件特性做优化只能使用保守的通用策略。在 Hexagon HTP 编译器中这导致了多个层面的差异1. Spill/Fill 缓冲区大小错误指标soc_model0soc_model77云编译Part 2 SpillFill (prompt)8,192,000~1,835,0081,835,008通用编译分配了 8 MB 的 spill/fill 缓冲区而针对 SA8775P 优化后只需要 ~1.8 MB。spill/fill 缓冲区用于在 VTCM片上高速缓存不够时溢出中间数据。错误的缓冲区大小意味着数据在 VTCM 和主存之间的搬运策略完全不同。2. 操作调度不同通用编译器不了解 Hexagon v73 的具体流水线特性如 HVX 向量单元的宽度、VTCM bank 数量无法对操作进行最优排序。这影响了中间结果的数值精度。3. 自回归累积误差前面两个问题在单次前向传播prompt 阶段中表现不明显——所以第一个 token 往往看起来正确。但在逐 token 生成自回归阶段每个 token 的输出都依赖前一个 token 的 KV cache。微小的数值偏差在 28 层 Transformer 中逐步放大几个 token 后就完全偏离正轨。这解释了为什么现象是第一个 token 对后面乱码——这是自回归解码中误差累积的典型表现。4.2 为什么二进制大小是线索编译器的优化策略直接决定了 context binary 的结构spill/fill buffer 的大小、操作的调度顺序、以及数据在不同存储层级之间的搬运方式。这些差异都会反映在最终二进制文件的大小上。状态二进制总大小vs 云编译推理质量soc_model0, vtcm4MB4703 MB-17 MB (-0.36%)乱码soc_model0, vtcm8MB4710 MB-10 MB (-0.21%)乱码soc_model77, vtcm8MB4720.7 MB0.7 MB (0.01%)正确云编译~4720 MBbaseline正确17 MB 的差距看似微小但它代表的是编译器在内部做出了不同的决策。当本地编译的大小与云编译精确匹配时内部结构也一致——推理质量随之匹配。五、为什么这个参数这么难发现回顾整个调试过程有几个因素让soc_model成为最后一个被检查的地方5.1 没有报错soc_model0是一个合法的默认值。编译器不会警告你你没有指定目标芯片——它只是默默地用通用策略编译产出一个完全合法的 context binary。这个文件可以正常加载、正常执行、不触发任何错误日志。5.2 差异太微小4703 MB vs 4720 MB0.36% 的差异。如果你没有一个已知正确的参考值云编译的 4720 MB这个数字看起来完全合理。5.3 症状误导第一个 token 对后面乱码这个现象很容易被误解为量化精度问题假设 8编码缺失假设 4KV cache 配置错误tokenizer 不匹配假设 3这些方向都有一定的合理性足以消耗大量时间去排查。5.4 文档不突出在 QAIRT SDK 文档中soc_model是HtpDeviceConfig的众多参数之一。文档没有特别强调不设置此参数会导致推理乱码——因为在手机端Snapdragon的典型场景中编译通常在设备上或通过 AI Hub 云端完成soc_model会被自动设置。本地离线编译是一个非典型场景而在这个场景下soc_model的默认值是致命的。六、完整的正确编译配置importqairtfromqairt.api.compiler.configimportCompileConfigfromqairt.api.common.backends.htp.configimport(HtpGraphConfig,HtpContextConfig,HtpDeviceConfig,HtpDeviceCoreConfig,PerfProfile,)# 转换 ONNX → DLCprompt_modelqairt.convert(prompt_onnx,encodingsprompt_enc,float_bitwidth16)token_modelqairt.convert(token_onnx,encodingstoken_enc,float_bitwidth16)# 编译 DLC → context binaryconfigCompileConfig(backendHTP,graph_custom_configs[HtpGraphConfig(nameprompt_ar128_cl4096_N_of_6,optimization_type3,weights_packingTrue,vtcm_size_in_mb8),HtpGraphConfig(nametoken_ar1_cl4096_N_of_6,optimization_type3,weights_packingTrue,vtcm_size_in_mb8),],context_custom_configs[HtpContextConfig(weight_sharing_enabledTrue)],device_custom_configs[HtpDeviceConfig(soc_model77,# SA8775P — 必须指定dsp_archv73,# Hexagon v73 — 必须指定cores[HtpDeviceCoreConfig(perf_profilePerfProfile.BURST,rpc_control_latency100)],)],)resultqairt.compile([prompt_model,token_model],configconfig)result.save(qwen2_5_7b_instruct_part_N_of_6.bin)设备端的htp_backend_ext_config.json也需要对应设置{devices:[{soc_model:77,dsp_arch:v73,cores:[{perf_profile:burst,core_id:0,rpc_control_latency:100}]}],memory:{mem_type:shared_buffer},context:{weight_sharing_enabled:true}}soc_model在两个地方都要设编译时CompileConfig决定编译器如何优化运行时htp_backend_ext_config.json决定 Genie 如何初始化 HTP 后端两处不一致同样可能产生问题。七、soc_model速查表SoCsoc_modeldsp_arch典型设备SA8775P77v73QCS9100 / QCS9075, Thundercomm RideQCS8550(查 SDK)v73Snapdragon 8 Gen 3SA8295P(查 SDK)v69高通座舱平台完整列表见 QAIRT SDK 文档docs/QNN/general/overview.html的 Supported Devices 表。八、关键经验总结8.1 默认值可以是致命的soc_model0不会报错、不会警告、不会中止编译。它产出一个看起来完全正常的文件但推理结果是错的。在嵌入式 AI 部署中“编译成功不等于编译正确”。8.2 二进制大小是你最好的调试指标在整个调试过程中二进制大小是唯一一个稳定指向根因的信号。云编译产出 4720 MB只要本地编译不是 4720 MB就说明编译器做了不同的事情。这个规则在多个场景中反复验证体积差 2 倍 → 4-bit weights 没打包详见 SMMU 限制突破记录体积差 0.36% →soc_model不对本文体积差 0.01% → 编译正确8.3 第一个 token 正确 ≠ 模型正确自回归 LLM 的 prompt 阶段第一个 token是单次前向传播数值误差还没有机会累积。只有在逐 token 生成阶段KV cache 中的误差才会被反复放大。永远不要因为第一个 token 看起来对就停止排查。8.4 建立一个已知正确的参考值整个调试过程之所以能最终收敛是因为我们有一个已知正确的参考值——云编译版本的 4720 MB 和其正确的推理输出。没有这个基准很难判断本地编译的产物是否足够接近。在部署新模型时先用云编译或官方预编译版本建立基线再尝试本地编译优化。8.5 排除法的价值虽然前 8 次假设都失败了但每一次排除都缩小了搜索空间排除了拆分策略假设 1排除了 SDK 版本假设 2排除了配置参数假设 3、4、5排除了量化差异假设 6、8最终只剩下编译器在底层做了不同的事情这一个方向。soc_model就在这个方向的终点。九、附录9 次假设完整对照表#假设执行动作结果关键排除证据111-split 结构不兼容改 6-split结构错误消失乱码依旧split 策略影响结构不影响数值2QAIRT 版本混用统一 2.42DLC 大小不变乱码依旧converter 版本差异可忽略3n-vocab 配置错误改 131072无效果模型始终输出 152064 logits4Gather 编码被移除手动恢复DLC 大小不变乱码依旧编码对嵌入层影响有限5浮点回退未启用qairt-quantizer已自动应用converter 默认处理6VTCM 4 MB 太小改 8 MB大小 4703→4710乱码依旧方向正确但不是根因7需要云端特殊步骤远程编译上传速度 200 KB/s放弃N/A8云端量化参数是关键分析源码未编码权重仅 0.28 MB贡献 1%不是主因9二进制大小差异反向追踪发现 soc_model017 MB 编译策略差异一行配置的教训soc_model77。这个参数在 QAIRT SDK 文档中没有被特别标注在手机端等典型场景中会被自动设置但在本地离线编译场景中不设置它会导致推理结果完全错误且没有任何报错提示。
返回列表