快速结论:这是 llama.cpp 在 Mali-G925 Vulkan 后端运行 Qwen3.5 多模态模型时,前向填充阶段产生全 NaN logits 的数值缺陷,优先缩小 MUL_MAT 小 tile 的匹配范围(m≤32 且 n>32 时改用 medium tile)即可解决。
适用环境:llama.cpp b9754 (52b3df0),Android JNI 构建,Vulkan 后端;已确认硬件:vivo X200 Pro (Dimensity 9400, Mali-G925-Immortalis MC12, Android 16/API 36);同报错也出现在骁龙 8 Elite 上(Qwen3.5 2B)。模型为 Qwen3.5-0.8B-Q4_K_M.gguf + mmproj-F16.gguf。
最快修复方案:暂无合并到上游的一步修复方案。唯一已验证有效的处理是本地修改 ggml-vulkan 的 tile 选择逻辑:将 ggml_vk_use_small_tile 的条件从 (m <= 32 || n <= 32) 改为在 ARM Mali(vendorID 0x13B5)且非 coopmat2 设备上,当 m <= 32 && n > 32 时返回 false,使该形状落入 medium tile。
注意事项:该修复仅在报告者的设备上验证;作者明确表示未在其它厂商 GPU 上验证,也不建议主动干预。建议不要简单改成 AND 条件,否则会把 m>32 且 n≤32 的长条形状也切到 medium tile,导致性能稀释。可用环境变量 GGML_VK_ALLOW_NARROW_MUL_MAT=1 恢复到上游行为用于对比测试。
问题场景
在 Android 设备(Mali-G925)上通过 Vulkan 后端运行 Qwen3.5-0.8B 多模态模型,提交图片+文本提示,模型将所有层(n_gpu_layers=99)卸载到 Vulkan,CLIP 视觉编码器留在 CPU(mmproj_use_gpu=false)。前向填充阶段(prefill)产生全部 248320/248320 个 NaN logits,采样退化并持续输出 ! 符号。同一二进制仅将设备策略切换为 CPU 后,同样输入可产出正常 logits。
报错原文
Eval bug: Vulkan (Mali-G925 / Immortalis MC12): Qwen3.5-0.8B multimodal prefill returns all-NaN logits, while the same build on CPU devices works
[PROBE] CLIP_embd: OK n=230400 zero=0 min=-4.18883 max=0.919084 mean=-0.00249603 mean_abs=0.0735539
[PROBE] eval_chunks: ret=0 n_past=184 | chunks n_tokens=394 n_pos=184
[PROBE] prefill_logits: *** CORRUPTED *** n=248320 nan=248320 inf=0 zero=0 min=3.4e+38 max=-3.4e+38 mean=0 mean_abs=0
[PROBE] prefill_logits head[8]: nan nan nan nan nan nan nan nan
[PROBE] sampled[0] token_id=0 piece='!'
raw output: !!!!!!!!!!!!!!!!!!!!!!!
原因分析
问题根因锁定在层 0 的 chunked Gated Delta Net(GDN)路径。调查证据链如下:
- CLIP 嵌入在两次运行中字节级一致(max_abs=0),说明视觉编码器不是问题源头。
- 数值回调显示首个 NaN 出现在层 0 chunked GDN 输出附近;由于层 0 是循环层、完整注意力 M-RoPE 直到层 3 才出现,可排除 M-RoPE 缺失导致。
- 原始嵌入 A/B 测试(正常 token 路径与 batch.embd 路径输入完全一致)表明输入数据和布局、宿主编排均正常,问题缩小到 Vulkan 上的层 0 GDN/循环路径计算。
- 报告者最终确认根因:Qwen3.5 Gated Delta Net 中 alpha/beta 是 16 通道窄投影(shape [1024,16]),在 Mali-G925 Vulkan 上命中
GGML_OP_MUL_MAT的“窄输出 + 长 prefill”数值缺陷,small tile shader 在 m≤32 且 n>32 时产生错误结果。
已排除的假设:GGML_VK_DISABLE_F16、修改 reduction 实现、禁用 fused GDN 走分解图、将 fused GDN 移到 CPU,均无法消除 NaN。图形分割为 2 且两次复现字节级一致,可排除调度抖动或竞态条件。
环境排查
- 确认 llama.cpp 版本:b9754 (52b3df0),Android JNI 构建。
- 确认 GPU 厂商 ID:ARM Mali vendorID 0x13B5(Mali-G925-Immortalis MC12)。
- 确认 Vulkan 后端版本:Vulkan 1.3。
- 确认是否启用 coopmat2(
device->coopmat2)——修复逻辑仅对非 coopmat2 设备生效。 - 确认模型结构:Qwen3.5-0.8B 使用 Gated Delta Net 混合线性注意力,日志中应出现
fused Gated Delta Net (autoregressive)和fused Gated Delta Net (chunked)启用提示。 - 可用
GGML_VK_ALLOW_NARROW_MUL_MAT=1恢复到上游 tile 选择行为,用于复现缺陷或对比验证。
解决步骤
- 备份原始
ggml-vulkan中的 matmul pipeline 选择文件。 - 修改
ggml_vk_use_small_tile函数:先读取环境变量GGML_VK_ALLOW_NARROW_MUL_MAT,若非零则直接使用上游条件(m <= 32 || n <= 32)。 - 在非强制模式下,当满足
device->vendor_id == VK_VENDOR_ID_ARM_ && !device->coopmat2时,对m <= 32 && n > 32返回 false(不使用 small tile);其余情况仍按上游条件判断。 - 重新编译 Android JNI 构建并部署到设备。
- 使用相同模型和输入复现测试,确认 prefill logits 不再全 NaN。
验证方法
运行同一图片+文本提示,观察 prefill logits probe 输出:期望从 *** CORRUPTED *** n=248320 nan=248320 变为 OK n=248320 zero=0 min=-11.1491 max=21.9672 mean=-0.196356 mean_abs=1.899,且 head[8] 显示有限数值。输出文本应不再持续输出 !。可用 GGML_VK_ALLOW_NARROW_MUL_MAT=1 确认能恢复到缺陷状态,再移除该变量确认修复生效。
参考来源
AI 工具推荐
想把多个 AI 模型放在一个入口?
GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。
推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。
这个方案解决了吗?
可以继续搜索完整报错,或查看同一工具的其他排查指南。
![[Bug]: parsing markdown error: failed to encode response: json: unsupported value: NaN (status code: 500)](https://www.chat-gpts.plus/wp-content/uploads/2026/08/15392-bea47837-768x403.jpg)

