ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

ik_llama.cpp 多 GPU 场景 CUDA「illegal memory access」排查实录:从 PR 438 失败修复到 MMVQ 内核根因定位

ik_llama.cpp 多 GPU 场景 CUDA「illegal memory access」排查实录:从 PR 438 失败修复到 MMVQ 内核根因定位 人工智能大模型推理引擎本地部署模型量化模型优化【免费下载链接】ik_llama.cppllama.cpp fork with additional SOTA quants and improved performance项目地址https://gitcode.com/GitHub_Trending/ik/ik_llama.cpp点击查看免费下载导读本文围绕 ik_llama.cpp 仓库中编号为 #438 的 Pull Request一次未成功的「非法内存访问」修复尝试展开还原其针对 issue #398、#425 的完整排查过程。读者将理解为什么-fmoe与多 GPU 部分卸载组合下会偶发CUDA error: an illegal memory access、CUDA 内核错误「异步上报」的语义陷阱以及最终通过compute-sanitizer定位到矩阵-向量乘法MMVQ内核在 2~3 行输入时越界读写的根因同时可获取一套可直接复用的规避手段与多 GPU MoE 调优参数。一、问题全貌两个反复出现的崩溃报告PR #438 的出发点是修复两个长期困扰社区用户的 CUDA 崩溃报告issue #398用户pt13762104报告使用Qwen3-30B-A3B配合-fmoe时运行一段时间后必然出现「illegal memory access」去掉-fmoe则完全正常。其环境为双路 Tesla T4compute capability 7.5无 BF16 支持复现命令为ik_llama.cpp/build/bin/llama-server -m /root/Qwen3-30B-A3B-UD-Q4_K_XL.gguf -c 32768 -fmoe -fa -ngl 99崩溃日志指向了 MoE 专家前向的核心函数当时版本行号CUDA error: an illegal memory access was encountered current device: 1, in function ggml_cuda_up_gate_unary at ggml/src/ggml-cuda.cu:2555 cudaMemcpyAsync(ids_host.data(), ids_dev, ggml_nbytes(ids), cudaMemcpyDeviceToHost, stream)issue #425用户nux报告DeepSeek-V3-0324IQ4_K_R4量化在llama-server处理 prompt 时崩溃ciprianveg用 4 卡2×3090 2×A4000跑Qwen3-235B-A22B-UD复现Lissanro用 4×3090 跑DeepSeek-R1T-Chimera周期性复现。错误几乎总出现在同一个位置CUDA error: an illegal memory access was encountered current device: 0, in function ggml_backend_cuda_synchronize at ggml/src/ggml-cuda.cu:3067 cudaStreamSynchronize(cuda_ctx-stream())两条 issue 的共性特征非常明显崩溃与-fmoe强相关但不绝对#425 中移除-fmoe后仍可能崩溃、多 GPU 场景更容易触发、单 GPU 很难复现、崩溃报错点高度集中于后端的同步/拷贝调用——而这恰恰是后续误导排查方向的关键。二、PR #438一次「不抱希望」的修复尝试PR #438作者ikawrakow2025-05-20 创建2025-05-23 关闭状态为Closed即未合入主线在描述中非常坦诚Attempt to fix #398, #425. My hopes are not very high, but it is better to try.其改动内容共两项对ffn_down融合条件做更严格的检查在-fmoe模式下专家前向ffn_up/ffn_gate与下投影ffn_down可以被融合进同一个 kernel 执行。作者加宽了对「确实可以融合ffn_down操作」的校验防止在部分张量留在 CPU、部分卸载到 GPU 的异构布局下做出错误的融合决策。作者自述这一改动在自己机器上「没有产生任何效果」因为他从未崩溃过但值得一试。吸收若干 mainline llama.cpp 后端的改动作者从上游后端挑选了几处改动但坦言「没有一个看起来特别有希望」属于广撒网式排查。测试反馈很快到来结果是否定的nux重新构建后依然在ggml_backend_cuda_synchronize处崩溃ggml-cuda.cu:3073ciprianveg在ggml-cuda.cu:3075处同样崩溃device 2schynce在分支ik/desperate_bug_fix_attempt即 #438 对应分支上用Qwen3-235B-A22B-IQ4_XS几乎立即崩溃divine-taco一度报告「30 轮长上下文对话未再出现」但随后在自动化补全测试中再次触发且观察到「失败频率似乎降低但并未根除」。作者于 2025-05-20 给出了明确结论OK, thanks. So #438 does not fix it.结论一PR #438 是一次失败但信息量极大的修复尝试。它排除了「ffn_down融合检查不严」和「mainline 后端差异」这两条假设为后续在 PR #442 中真正定位根因积累了实验数据。三、排查陷入误区的根源CUDA 内核错误的「异步上报」语义为什么所有报告的错误都出现在ggml_backend_cuda_synchronize/cudaStreamSynchronize/cudaMemcpyAsync这类「看起来与专家计算无关」的调用上作者在 #425 的收尾阶段点破了这个认知误区我昨天才意识到在启动launch一个 CUDA kernel 之后检查错误并不代表 kernel 已经成功执行只代表 kernel 已成功「排队」等待执行。如果 kernel 内部存在缺陷例如非法内存访问产生的错误会在之后的某个调用中被报告。这正是 CUDA 异步执行模型的核心语义cudaLaunchKernel只是把 kernel 提交到设备的流stream中立即返回如果 kernel 在设备端执行时发生越界读写CUDA 上下文会被「毒化」错误状态被记录直到下一次同步点cudaStreamSynchronize、cudaMemcpyAsync之后的错误检查等才会被主机端观察到因此报错位置 ≠ 出错位置。所有「错误发生在 synchronize/copy 处」的日志都只是说明「此前某个 kernel 已经出错此刻才被察觉」。正是这一语义让作者一度怀疑是后端调度器ggml_backend_sched_compute_splits见 ggml-backend.c在跨设备拷贝张量时出了问题——毕竟ggml_backend_sched_compute_splits的职责正是遍历计算图、把每个算子split的输入张量按需拷贝到对应后端并在跨后端依赖处同步相关调用链可见于 src/llama.cpp 的llama_decode与 examples/server/server.cpp 的server_context::update_slots。作者在 #425 中先后排查过的「假想敌」包括调度器拷贝逻辑、跨设备 peer-to-peer 访问、张量元数据错乱、prompt 内容/聊天模板差异、上下文长度、u-batch 奇偶性等均被一一否定或无法稳定复现。四、逐层逼近真凶的调试工具箱从 #398 到 #425 收尾社区与作者协同使用了一整套调试手段这些方法对任何「CUDA 偶发崩溃」类问题都具备直接迁移价值4.1 gdb 回溯确认崩溃现场但不足以定位根因作者建议以RelWithDebInfo构建并挂入 gdbcmake --build ./build --config RelWithDebInfo -j $(nproc) gdb --args ./build/bin/llama-server 你触发崩溃的完整命令崩溃时backtrace显示调用链为llama_decode → llama_graph_compute → ggml_backend_sched_graph_compute_async → ggml_backend_sched_compute_splits → ggml_backend_synchronize → ggml_backend_cuda_synchronize → cudaStreamSynchronize → ggml_cuda_error → ggml_abort并在ggml_backend_sched_compute_splits帧内打印输入张量p *input得到l_out-42、inp_pos、KQ_mask (copy)等张量信息——但作者坦言「仅凭回溯无法诊断」。结论二gdb 能确认「错误在同步点暴露」却无法指出「哪个 kernel 真正越界」因为错误的「发源地」已经执行完毕。4.2 compute-sanitizercuda-memcheck一击命中最终突破口来自ciprianveg配合运行的 CUDA 内存检测器cuda-memcheck your_server_command # 或新版 compute-sanitizer输出直接给出了越界访问的精确位置 Invalid __global__ read of size 2 bytes at void mul_mat_vec_q(ggml_type)12, (int)2, (int)4(const void *, const void *, float *, const char *, int, int, int, int, unsigned long, unsigned long, unsigned long, long)0x540 by thread (8,0,0) in block (779,0,0) Address 0x7f788a3f7a0c is out of bounds and is 50,701 bytes after the nearest allocation at 0x7f744c000000 of size 18,224,165,888 bytes Saved host backtrace up to driver entry point at kernel launch time Host Frame: ggml_cuda_op_mul_mat_vec_q(...) in libggml.so Host Frame: ggml_cuda_op_mul_mat(...) in libggml.so Host Frame: ggml_cuda_up_gate_unary(...) in libggml.so Host Frame: ggml_backend_cuda_graph_compute(...) in libggml.so关键信息有三条出错 kernel 是mul_mat_vec_q矩阵-向量乘法模板参数(ggml_type)12即GGML_TYPE_Q4_K后两个参数与量化分块布局相关访问地址超出最近一次分配的缓冲区约5 万字节即指针计算越界而非悬垂指针主机侧调用栈证实其入口正是ggml_cuda_up_gate_unary该函数在 ggml/src/ggml-cuda.cu 中依然存在当前位于第 3580 行附近随版本演进行号已与 2025 年 5 月报告中的 2555/2764/3073 等不同。结论三compute-sanitizer把「在同步点暴露的错误」翻译回了「kernel 内部的越界读」这是整个排查中最关键的一步。4.3 其他被尝试并排除的手段CUDA call tracePR #442 附带打印崩溃前最近几十万次 CUDA 调用确认「拷贝之后立即同步即崩溃」的模式指向拷贝前的某个 kernelprintf 调试在ggml_backend_cuda_synchronize中打印「当前设备/上下文设备不一致」等信息发现大量attempt to copy from device X to device Y without access enabled警告-DGGML_CUDA_NO_PEER_COPY1禁用 peer-to-peer 拷贝后出现新的加载期段错误随即放弃该方向作者也指出 mainline llama.cpp 中并未显式启用 peer access因此这些警告并非崩溃根因-DGGML_SCHED_MAX_COPIES1降低计算图跨设备拷贝副本数可显著减小 compute buffer但对崩溃本身无效作者在 #425 中建议该选项主要出于显存与性能考虑张量元数据打印怀疑UDUnsloth 动态量化模型中ffn_down_exps与ffn_up/gate_exps位宽不同导致元数据错用实测打印blk.42.ffn_up_exps.weight, q4_K, 4096 x 1536 x 128等元数据后未发现错乱。五、根因定位MMVQ 内核在 2~3 行token时的越界访问在排除了调度器、拷贝、元数据等全部假设后作者于 2025-05-23 给出了最终结论真正的 bug 出在矩阵-向量乘法matrix-vector multiplication内核中。它只在**同时处理 2 个或 3 个行即 token**时触发该内核令人困惑地最多处理 8 行。这不用于 TG生成阶段只在某个专家最终只分到 2~3 行时才触发因此极其罕见。结合 ggml/src/ggml-cuda/mmvq.cu 与 ggml/src/ggml-cuda/dmmv.cu 的源码结构可以看到ik_llama.cpp 的专家前向ggml_cuda_up_gate_unary在-fmoe模式下会把ffn_up与ffn_gate的矩阵乘合并调度当批量很小如生成阶段某个 batch 内专家分到的行数极少时走mul_mat_vec_q系列 kernel这些 kernel 按固定分块block与线程布局遍历量化矩阵的行列当行数不是分块布局的整数倍时边界计算出现缺口产生越界读。这正是compute-sanitizer报告的mul_mat_vec_q(ggml_type)12, ...地址越界数万字节的由来。修复落在PR #442的一个独立提交中Fix bug in MMVQ kernel commit b79be8a191c10883a84d725ae9e70ec693ab3b6b用户Lissanro专门做了对照实验只应用这一个提交此前 100% 复现的崩溃先无思考生成、再带think重新生成即不再触发确认该提交即为真正修复。作者也据此说明#442上的其他改动同步追踪、设备拷贝警告等只是诊断工具「均非必需」。结论四最终结论本次「illegal memory access」的完整链条是——-fmoe专家前向 → 小批量走 MMVQ kernel → 2~3 行时越界读 → CUDA 上下文被污染 → 错误在下一个同步点ggml_backend_cuda_synchronize被报告 → 被误判为「后端/拷贝问题」。PR #438 的两项改动与该根因无关因此必然无效。六、实战规避与调优建议多 GPU MoE 用户虽然根因已修复但排查过程中沉淀下来的一批规避与性能调优经验对仍在旧版本上运行或需要榨干 MoE 模型性能的用户仍然非常有价值6.1-fmoe与量化类型的搭配#398 中作者指出一个关键观察IQX_K如mix-IQ3_K这类没有 CUDA 量化矩阵乘实现的量化矩阵乘会走dequantize → cuBLAS路径因此不触发崩溃而IQ4_XS、UD-Q3/Q4_K_XL等有量化 MMVQ 实现的类型更容易踩中该 bug。这间接印证了「问题在量化矩阵乘内核」的判断。遇到莫名崩溃时可尝试换用走 cuBLAS 路径的量化做交叉验证。6.2 恢复 PR #405 之前的卸载策略-op 29,0divine-taco发现 PR #405 改变了专家 op 的卸载策略之前张量在 CPU 上时融合的ffn_up/ffn_gateop 不会被卸载到 GPU之后会。用-op 29,0可临时恢复旧行为能显著降低崩溃频率虽然并未根治约 15 轮后仍会触发。对于受影响的旧版本这是一个有效的降级开关。6.3-ot正则的精细度直接影响 TG 性能与图分裂数作者实测单卡 RTX-4080Qwen3-30B-A3Btensor override 正则图分裂数graph splitsTG 速度-ot blk\.[3-4][0-9].ffn_.*_expsCPU3870.4 t/s-ot blk\.[3-4][0-9].ffn.*CPU7466.7 t/s原因后者把ffn_gate_inp、ffn_norm等小张量也留在 CPU增加了跨设备图分裂次数。建议只把大专家张量ffn_*_exps留在 CPU小张量仍放 GPU。ciprianveg按此调整后 TG 提升了约 6%。6.4 大 u-batch 对 MoE 模型 PP 速度的显著收益作者在 #425 中解释了 MoE 与稠密模型的本质差异u-batch512 时 DeepSeek-V3 每层激活512 × 8 4096个专家每个专家平均只处理 16 行行数过少的矩阵乘效率极低增大 u-batch 让每个专家处理更多行PP 吞吐显著提升。实测-b 3072 -ub 3072相比 1024 从 80 t/s 提升到 180 t/s-b 4096 -ub 4096还可再提升 10~20%。代价是更大的 CUDA compute buffer——若显存不足可以每块 GPU 少卸载一层以换取大 u-batch。不建议超过 4096上游在 u-batch8192 时发现过其他 bug。6.5 编译选项-DGGML_SCHED_MAX_COPIES1该选项限制计算图跨设备拷贝的副本数能把compute buffer从「每 GPU 近 20 GB」降到可接受水平Lissanro借此才能使用-b 4096 -ub 4096与 64K~80K 上下文。对多 GPU 异构场景属于「显存急救」选项。6.6 崩溃频发时的快速二分法综合多位用户的有效实验快速定位是否为「本 bug」的手段包括单 GPU 是否复现#398 确认单卡不崩、-fmoe开/关对比、-fa开/关对比、llama-cli与llama-server对比#425 中sweep-bench/cli均不崩而 server 崩、替换走 cuBLAS 路径的量化。七、从当前源码回看这场排查如今仓库中的实现已经历后续演进但关键代码路径清晰可查ggml/src/ggml-cuda.cuggml_cuda_up_gate_unary当前约第 3580 行仍是-fmoe专家前向融合的核心负责把ffn_up/ffn_gate的矩阵乘与ffn_down按融合条件统一调度本次排查中所有「报错现场」都指向它但真正的越界发生在其内部调用的量化矩阵乘 kernel。ggml/src/ggml-cuda/mmvq.cuggml_cuda_op_mul_mat_vec_q_impl以mmvq_args结构体含nrows_x、nrows_y、ncols_x等边界参数驱动模板化mul_mat_vec_q*kernel覆盖 Q4_0/Q4_1/Q5_0/Q8_0/Q2_K/Q3_K/Q4_K/Q5_K/Q6_K 等量化类型——正是 PR #442 修复 bug 的所在地。ggml/src/ggml-cuda/dmmv.cudequantize_mul_mat_vec_q*系列为无量化实现的类型提供「反量化 cuBLAS」路径即 #398 中IQX_K不崩溃的原因所在。结语PR #438 是一份典型的「失败但诚实」的开源修复记录它排除了两个错误假设并催生了社区与作者之间一轮高质量的协同调试——从 gdb 回溯、CUDA call trace、peer copy 检查到compute-sanitizer的致命一击最终在 PR #442 的Fix bug in MMVQ kernel提交中根治。整个过程最重要的方法论启示是在 CUDA 编程中错误报错点从来不是错误发生点同步调用只是「受害者」真正的「凶手」永远要回到 kernel 内部、用内存检测工具去找。对于仍在多 GPU MoE 场景部署 ik_llama.cpp 的开发者本文第六节整理的规避手段与调优参数是这场排查留下的最实用遗产。赞分享人工智能大模型推理引擎本地部署模型量化模型优化【免费下载链接】ik_llama.cppllama.cpp fork with additional SOTA quants and improved performance项目地址https://gitcode.com/GitHub_Trending/ik/ik_llama.cpp点击查看免费下载相关推荐ik_llama.cpp 多 GPU 部署排障实录模型加载到 CUDA1 触发 Illegal Memory Access 的根因与 -mg 主 GPU 参数ik_llama.cpp 多 GPU 部署排障实录模型加载到 CUDA1 触发 Illegal Memory Access 的根因与 mg 主 GPU 参数人工智能大模型推理引擎本地部署模型量化模型优化ik_llama.cpp CUDA 非法内存访问崩溃全记录:MoE 模型 MMVQ 内核 2/3 行越界 Bug 的排查与修复ik_llama.cpp CUDA 非法内存访问崩溃全记录:MoE 模型 MMVQ 内核 2/3 行越界 Bug 的排查与修复 本文基于 ik_llama.cp人工智能大模型推理引擎本地部署模型量化模型优化ik_llama.cpp 编译故障排查GGML_IQK_FA_ALL_QUANTS 全量化 FA 内核导致编译失败的根因与修复历程ik_llama.cpp 编译故障排查GGML_IQK_FA_ALL_QUANTS 全量化 FA 内核导致编译失败的根因与修复历程 导读 本文围绕 ik_ll人工智能大模型推理引擎本地部署模型量化模型优化创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表