机器: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:

Get-PnpDevice -PresentOnly |
  Where-Object { $_.FriendlyName -match 'NPU|Neural' -or $_.Class -eq 'NeuralProcessors' }

结果只有蓝牙和输入设备,没有 NPU。加上网上普遍说 Arrow Lake-HX 系列(游戏本)不带
NPU 单元,我差点就下了"这台机器没有 NPU"的结论。

同事提醒:任务管理器里明明能看到 NPU0 和 “Intel AI Boost”。重新用宽匹配一查:

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/)是单设备槽设计,设备在初始化时
确定。源码里找到了关键逻辑:

// 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");

三个重要事实:

  1. 默认设备是 CPU,不是核显——之前测试里那个 32GB 就是系统内存。OpenVINO 后端
    把 CPU 也当 OpenVINO 设备处理(走 OpenVINO 的 CPU 图编译,不是 ggml CPU)。
  2. 设备名来自环境变量 GGML_OPENVINO_DEVICE,可选 CPU / GPU / NPU
  3. NPU 有完整实现is_npu=true 时会启用专门的编译配置——动态量化、
    NPU_USE_NPUW(NPU 工作负载)、权重银行等一整套餐。

还看到一条特别实在的注释:

// 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 驱动更新。

技术收获

  1. 查询设备别用关键词匹配Intel(R) AI Boost 不含 “NPU”/“Neural” 字样,设备
    类别是 ComputeAccelerator。教训:先 Get-PnpDevice | Select FriendlyName, Class
    全量看一眼再写过滤。
  2. llama.cpp OpenVINO 后端的设备选择GGML_OPENVINO_DEVICE 环境变量
    (CPU/GPU/NPU,默认 CPU),初始化时读取并缓存;后端是单设备槽,--list-devices
    只显示一个 OPENVINO0
  3. "设备不可用"要区分三个层面:硬件存在 ≠ OpenVINO 运行时能枚举 ≠ llama.cpp
    后端能用。这次三个层面全验过,用 20 行 ctypes 调 ov_core_get_available_devices
    是最干净的验证手段。
  4. llama.cpp OpenVINO 后端对 VLM 支持不完整:多模态模型(Qwen-VL 架构)的
    cache_r_* 缓存张量会触发不支持的 CPY 操作,加载即失败。等上游补齐算子或自己
    编译补丁之前,视觉模型别走 OpenVINO。
  5. NPU 内存是真的小:官方注释原话 “NPU memory is too small even for kvcache”,
    就算算子问题解决了,长上下文也是硬伤。

附录:关键命令

# 确认 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

更多推荐