机器:Intel Core Ultra 9 275HX + RTX 5070 Ti Laptop
背景
我们的离线 PDF 转 Markdown 项目用 llama.cpp 加载 OvisOCR2(0.8B 视觉文档解析模型),
CUDA 后端 + RTX 5070 Ti 已经跑得飞快(单页约 2 秒)。某天听到一个说法:llama.cpp
可以用 Intel CPU 内置的 NPU 当推理后端,底层是 OpenVINO 。核显之外的免费算力,听着
很诱人------于是有了这次探索。结论先放前面:最终没跑成,但把整条链路摸了个底朝天,
踩了两个很有意思的坑。过程比结果更有价值。
第一回合:没找到 NPU?
第一步自然是确认硬件。Windows 下查设备,我写了这条 PowerShell:
powershell
Get-PnpDevice -PresentOnly |
Where-Object { $_.FriendlyName -match 'NPU|Neural' -or $_.Class -eq 'NeuralProcessors' }
结果只有蓝牙和输入设备,没有 NPU。加上网上普遍说 Arrow Lake-HX 系列(游戏本)不带
NPU 单元,我差点就下了"这台机器没有 NPU"的结论。
同事提醒:任务管理器里明明能看到 NPU0 和 "Intel AI Boost"。重新用宽匹配一查:
powershell
Get-PnpDevice -PresentOnly | Where-Object { $_.FriendlyName -match 'AI Boost' }
# OK ComputeAccelerator Intel(R) AI Boost
设备就在那里,PCI ID VEN_8086&DEV_AD1D(Arrow Lake 的 NPU 控制器)。问题出在我的
查询 :Intel 管 NPU 叫 "AI Boost",名字里既没有 "NPU" 也没有 "Neural";它的设备类别
是 ComputeAccelerator,不是我以为的 NeuralProcessors。一次想当然的查询,差点得出
一个错误结论。经验第一条:别用关键词猜设备名,先把设备列表全量拉出来看。
第二回合:OpenVINO 后端确实存在
llama.cpp 官方 release 有 OpenVINO 构建(llama-b10360-bin-win-openvino-x64.zip,
76MB,自带 OpenVINO 2026.2.1 运行时)。下载解压后第一件事------枚举设备:
> llama-server.exe --list-devices
Available devices:
OPENVINO0: OpenVINO Runtime (32213 MiB, 10824 MiB free)
只有一个设备槽,显示 32GB 内存------当时我以为是核显的共享显存。又是想当然 。
后来挖源码才知道这个数字是系统内存,因为后端默认设备是 CPU(详见下节)。
为了彻底搞清 OpenVINO 能看到什么,写了个 20 行的 ctypes 脚本直接调 OpenVINO C API:
ov_core_create: 0
get_available_devices: 0 count = 4
CPU
GPU.0
GPU.1
NPU
NPU 赫然在列。硬件在、驱动在、OpenVINO 运行时也认------问题只剩 llama.cpp 后端怎么选设备。
第三回合:挖源码,设备选择机制
llama.cpp 的 OpenVINO 后端(ggml/src/ggml-openvino/)是单设备槽设计,设备在初始化时
确定。源码里找到了关键逻辑:
cpp
// ggml-openvino-extra.cpp
device_name = ggml_openvino_getenv_str("GGML_OPENVINO_DEVICE", "CPU");
auto available_devices = ov_singleton_core().get_available_devices();
if (std::find(...) == available_devices.end()) {
GGML_LOG_WARN("device %s is not available, fallback to CPU\n", device_name);
device_name = "CPU";
}
is_npu = (device_name == "NPU");
三个重要事实:
- 默认设备是 CPU ,不是核显------之前测试里那个 32GB 就是系统内存。OpenVINO 后端
把 CPU 也当 OpenVINO 设备处理(走 OpenVINO 的 CPU 图编译,不是 ggml CPU)。 - 设备名来自环境变量
GGML_OPENVINO_DEVICE,可选CPU/GPU/NPU。 - NPU 有完整实现 :
is_npu=true时会启用专门的编译配置------动态量化、
NPU_USE_NPUW(NPU 工作负载)、权重银行等一整套餐。
还看到一条特别实在的注释:
cpp
// Put kvcache on device memory for GPU (NPU memory is too small even for kvcache)
NPU 内存小到连 KV cache 都放不下------Intel 自己人写的注释,早就预告了结局。
环境变量机制用对照实验验证:设一个不存在的设备名,会明确打警告:
W GGML OpenVINO Backend: device FOO is not available, fallback to CPU
而设 GGML_OPENVINO_DEVICE=NPU 时没有 任何警告------说明 NPU 通过了可用性检查,
被真正选中。机制是通的。
失败一:OvisOCR2 加载即崩(与设备无关)
正戏开始。用 NPU 加载 OvisOCR2(Q4_K_M + mmproj):
> GGML_OPENVINO_DEVICE=NPU llama-server.exe -m OvisOCR2-Q4_K_M.gguf --mmproj mmproj-F16.gguf -ngl 99
ggml-backend.cpp:930: pre-allocated tensor (cache_r_l0 (view) (copy of ))
in a buffer (OPENVINO0) that cannot run the operation (CPY)
模型加载阶段直接中止。cache_r_l0 是 Qwen-VL 架构主模型里的旋转位置编码缓存
(一个 view 张量),OpenVINO 后端不支持对它做 CPY 拷贝操作。
怀疑是 NPU 特有问题?测了一遍全设备:
| 设备 | 结果 |
|---|---|
| CPU(默认) | 同样 CPY 报错 |
NPU(GGML_OPENVINO_DEVICE=NPU) |
同样 CPY 报错 |
任意设备 + GGML_OPENVINO_ENABLE_FALLBACK=1 |
同样 CPY 报错 |
-ngl 0(纯 ggml CPU,不走 OpenVINO) |
✅ 正常加载 |
结论:CPY 是 OpenVINO 后端的通病,与设备无关 。这是 llama.cpp OpenVINO 后端对
多模态模型(VLM)的算子覆盖短板,GitHub 上有同类问题
(#22333)。视觉投影器 mmproj
只是替罪羊------去掉 mmproj 也一样挂,问题在主模型本身。
失败二:纯文本模型在 NPU 上推理崩溃
Ovis 跑不了,那 NPU 上能跑点别的吗?下载了个最小的纯文本模型
(Qwen2.5-0.5B Q4,428MB)试水。结果很有戏剧性:
-
OpenVINO CPU 设备:加载成功,推理正常------"What is 2+2?" → "2+2 equals 4."
-
NPU 设备 :模型加载成功 (过了 CPY 那道坎,说明 CPY 确实是视觉模型的专属问题),
但一推理就崩:ov::Exception: Option 'NPU_COMPILER_DYNAMIC_QUANTIZATION' is not supported
for current configuration
llama_decode failed, ret = -3 → 进程段错误退出(exit 139)
NPU_COMPILER_DYNAMIC_QUANTIZATION 是 llama.cpp 后端为 NPU 硬编码 的编译选项
({"NPU_COMPILER_DYNAMIC_QUANTIZATION", "YES"}),而第一代 AI Boost 单元(Arrow
Lake-HX 的 AD1D)或当前驱动不支持这个配置。没有环境变量能绕过------除非改源码重编译,
或者赌更新驱动后 OpenVINO 会对老硬件放宽配置。
总结:这次探索的收获
最终结论 :这台机器上,NPU 跑任何模型目前都走不通------Ovis 卡 CPY 算子(所有
OpenVINO 设备通病),纯文本模型卡 NPU 编译配置(硬件/驱动限制)。项目维持 CUDA +
5070 Ti 就是最优解。想再跟进,两个观察点:llama.cpp 上游对 OpenVINO 多模态支持的
修复、Intel NPU 驱动更新。
技术收获:
- 查询设备别用关键词匹配 。
Intel(R) AI Boost不含 "NPU"/"Neural" 字样,设备
类别是ComputeAccelerator。教训:先Get-PnpDevice | Select FriendlyName, Class
全量看一眼再写过滤。 - llama.cpp OpenVINO 后端的设备选择 :
GGML_OPENVINO_DEVICE环境变量
(CPU/GPU/NPU,默认 CPU),初始化时读取并缓存;后端是单设备槽,--list-devices
只显示一个OPENVINO0。 - "设备不可用"要区分三个层面 :硬件存在 ≠ OpenVINO 运行时能枚举 ≠ llama.cpp
后端能用。这次三个层面全验过,用 20 行 ctypes 调ov_core_get_available_devices
是最干净的验证手段。 - llama.cpp OpenVINO 后端对 VLM 支持不完整 :多模态模型(Qwen-VL 架构)的
cache_r_*缓存张量会触发不支持的 CPY 操作,加载即失败。等上游补齐算子或自己
编译补丁之前,视觉模型别走 OpenVINO。 - NPU 内存是真的小 :官方注释原话 "NPU memory is too small even for kvcache",
就算算子问题解决了,长上下文也是硬伤。
附录:关键命令
powershell
# 确认 NPU 硬件
Get-PnpDevice -PresentOnly | Where-Object { $_.FriendlyName -match 'AI Boost' }
# 枚举 OpenVINO 设备(需 openvino_c.dll,可用 ctypes 调 ov_core_get_available_devices)
# 用 NPU 跑 llama-server
GGML_OPENVINO_DEVICE=NPU llama-server.exe -m model.gguf -ngl 99
# 设备选择对照实验(应看到 fallback 警告)
GGML_OPENVINO_DEVICE=FOO llama-server.exe -m model.gguf -ngl 99