Eval bug: Vulkan (Mali-G925 / Immortalis MC12): Qwen3.5-0.8B multimodal prefill returns all-NaN logits, while the same build on CPU devices

这是 llama.cpp 在 Mali-G925 Vulkan 后端运行 Qwen3.5 多模态模型时,前向填充阶段产生全 NaN logits 的数值缺陷,优先缩小 MUL_MAT 小 tile 的匹配范围(m≤32 且 n>32 时改用 medium tile)即可解决。

快速结论:这是 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 选择行为,用于复现缺陷或对比验证。

解决步骤

  1. 备份原始 ggml-vulkan 中的 matmul pipeline 选择文件。
  2. 修改 ggml_vk_use_small_tile 函数:先读取环境变量 GGML_VK_ALLOW_NARROW_MUL_MAT,若非零则直接使用上游条件 (m <= 32 || n <= 32)
  3. 在非强制模式下,当满足 device->vendor_id == VK_VENDOR_ID_ARM_ && !device->coopmat2 时,对 m <= 32 && n > 32 返回 false(不使用 small tile);其余情况仍按上游条件判断。
  4. 重新编译 Android JNI 构建并部署到设备。
  5. 使用相同模型和输入复现测试,确认 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 确认能恢复到缺陷状态,再移除该变量确认修复生效。

参考来源

ggml-org/llama.cpp #26921

GamsGo AI

AI 工具推荐

想把多个 AI 模型放在一个入口?

GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。

了解 GamsGo AI

推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 20641

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注