快速结论:这个报错通常发生在 llama.cpp 加载模型(尤其是加载目标模型后再加载投机解码 draft 模型)时,某块 CUDA 设备通过 cudaMemGetInfo 报告 free == 0,导致 tensor split 归一化时出现除零、产生 NaN,最终把层分配到不存在的设备索引上。优先排查显存占用与 tensor split 计算逻辑。
适用环境:llama.cpp(llama-server,version 0.1.2-dev,build 1,commit 9d77fa1,GNU 15.2.0);Linux x86_64(WSL2,Ubuntu on Windows);GGML backend 为 CUDA;硬件为 NVIDIA GeForce RTX 4070 SUPER(12 GB),驱动 596.49,CUDA 13.3。
最快修复方案:Issue 讨论中已验证的修复是在 src/llama-model.cpp 的 load_tensors 中给归一化加零保护并回退到均分,对应提交 f6342de:
- splits[i] /= split_sum;
+ splits[i] = split_sum != 0.0f ? splits[i] / split_sum : float(i + 1) / n_devices();
注意事项:该修复解决的是除零导致的 NaN 和越界崩溃,并不解决“显存实际不足”本身;即使按均分正常分配,显存不足时仍可能返回内存分配错误。此外作者观察到 nvidia-smi 报告约 1.6 GiB 空闲,而进程内 cudaMemGetInfo 返回 0,这部分怀疑是 WSL2 的怪癖,未在 Issue 中确认。
问题场景
用户在 llama.cpp 中先用 -ngl 99 加载一个接近占满 12 GB 显存的目标模型(例如 Qwen3.8-27B UD-Q2_K_XL,约 9.8 GB),然后在同一进程里继续加载投机解码 draft 模型(-md <draft.gguf> --spec-type <draft-type> -ngld 99)。draft 模型加载阶段没有返回常规的显存分配失败,而是抛出 vector::_M_range_check 并中止。
报错原文
llama_model_load: error loading model: vector::_M_range_check: __n (which is 1) >= this->size() (which is 1)
PROBE mem: dev[0]=CUDA0 free=0 MiB total=12281 MiB
PROBE split: il=0 i_gpu_start=0 act_gpu_layers=6 n_gpu_layers=99 n_layer_all=5 n_devices=1 layer_gpu=1 val=0.000000 splits[0]=-nan
_ZSt24__throw_out_of_range_fmtPKcz
libllama.so.0(+0xc413d) -> llama_model_base::load_tensors(llama_model_loader&) [clone .cold]
libllama.so.0(+0xd7ae5) -> llama_model_load(...)
libllama.so.0(+0xd8c12) -> llama_model_load_from_file_impl(...)
libllama.so.0(llama_model_load_from_file+0xc8)
libllama-common.so.0(common_speculative_init_result::common_speculative_init_result(...))
libllama-server-impl.so(server_context_impl::load_model(common_params&))
原因分析
根因在 llama_model_base::load_tensors(src/llama-model.cpp)。当用户未设置 tensor_split 时,splits[i] 保存的是每块设备的空闲显存。归一化时先累加得到 split_sum,再执行 splits[i] /= split_sum;如果每块设备都报告 0 空闲字节,split_sum 为 0,就变成 0.0f / 0.0f,所有 split 点变成 NaN。
随后 std::upper_bound 在 NaN 序列上返回 end(),使 layer_gpu 等于 n_devices(),接着 devices.at(layer_gpu) 抛出 std::out_of_range,被包装成用户看到的 vector::_M_range_check 消息,容易误判为 GGUF 损坏。
代码中已有的回退分支只覆盖 free == 0 且 total == 0 的设备:
if (free == 0 && total == 0) {
ggml_backend_dev_memory(cpu_dev, &free, &total);
}
本案例中 total 为 12281 MiB、仅 free 为 0,因此不会触发回退。作者指出同一路径在任何“设备报告 0 空闲字节”的后端上都可能复现;“cudaMemGetInfo 返回 0 而 nvidia-smi 显示约 1.6 GiB 空闲”可能归因于 WSL2,但崩溃本身来自未加保护的除法。
环境排查
- 确认 llama.cpp 版本与 commit:
llama-server --version,本 Issue 为 version 0.1.2-dev,build 1,commit 9d77fa1。 - 确认是否使用 CUDA backend,以及编译所用工具链(本 Issue 为 GNU 15.2.0 for Linux x86_64)。
- 确认 GPU 型号、驱动与 CUDA 版本:RTX 4070 SUPER(12 GB)、驱动 596.49、CUDA 13.3。
- 确认运行环境是否 WSL2(Ubuntu on Windows),WSL2 下
cudaMemGetInfo的可用显存报告可能与nvidia-smi不一致。 - 确认是否显式设置了
tensor_split;未设置时该路径才会用设备空闲显存作为 split 依据。 - 确认是否在加载目标模型后于同进程加载 draft 模型(
-md/--spec-type/-ngld)。 - 确认出现
free=0 MiB的设备及其total是否非 0(可用 Issue 中的 PROBE 输出方式观察)。
解决步骤
- 先确认崩溃点信息:看到
vector::_M_range_check时不要直接判定 GGUF 损坏,检查是否有设备free=0 MiB且total非 0。 - 可优先尝试 Issue 中验证的补丁:在
src/llama-model.cpp的load_tensors中,将splits[i] /= split_sum;替换为带零保护的均分逻辑,或直接应用提交 f6342de。 - 重新编译 llama.cpp 后再复现同样的加载流程,观察是否不再出现 NaN split 与越界异常。
- 如果补丁后行为变为正常的内存分配失败提示,说明除零问题已解决,需要按显存不足的方向处理(例如减少
-ngl/-ngld、先卸载或减少目标模型占用、改用更小的量化)。 - 若仍怀疑 WSL2 下显存报告异常,可在 WSL2 环境对比
nvidia-smi与进程内cudaMemGetInfo的空闲值,判断是否需要调整运行环境;该点 Issue 中未给出确定结论。
验证方法
用与复现相同的参数再次加载目标模型与 draft 模型。若不再抛出 vector::_M_range_check / std::out_of_range,且 split 点不再为 NaN(例如 PROBE 输出中 splits[0] 不再是 -nan),则除零引发的越界崩溃已修复。此时如果显存确实不足,应看到明确的内存分配失败信息,而非 out_of_range 异常。
参考来源
fix commit f6342de11036b9e8f27b9f42c4e96d7331926f75
AI 工具推荐
想把多个 AI 模型放在一个入口?
GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。
推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。
这个方案解决了吗?
可以继续搜索完整报错,或查看同一工具的其他排查指南。


