环境
| 项 |
值 |
| DimAgent 桌面端 |
0.9.33 (0.9.33.0) |
内置 CLI (resources/runtime/cli/dim.exe) |
dimcode 0.5.6 (Bun 1.3.8 standalone) |
| Light OCR 组件 |
0.5.7-ppocrv6-small-native-20260719.1.desktop.1 (win32-x64) |
| 载荷 SHA-256 |
e3f3a14cfda8e2e9a27679d0ffc9e3096ada9102882c5ab9a6c9032a17fefcb9 |
| 系统 |
Windows 11 专业版 26200 (10.0.26200) x64 |
| CPU / 内存 |
Intel Core i7-14700F / 32 GB |
| GPU / 驱动 |
NVIDIA GeForce RTX 5060 Ti / 32.0.16.1692 (2026-09-04) |
现象
设置 → 功能 里的 Light OCR 一直显示「失败」,无法安装/启用。失败信息为:
FEATURE_VERIFY_FAILED: light-ocr: Command failed:
C:\...\DimAgent\resources\runtime\cli\dim.exe ocr recognize
C:\...\feature-runtime-fixtures\known-text.png
--provider light-ocr-local --model ppocrv6-small
--out-dir C:\Users\...\Temp\dimcode-feature-smoke-bVs0sP\known-text.png --json
============================================================
Bun v1.3.8 (b64edcb4) Windows x64
Features: Bun.stderr(2) Bun.stdin(2) Bun.stdout(2) jsc standalone_executable process_dlopen(2)
Elapsed: 34190ms | User: 3765ms | Sys: 28156ms
RSS: 0.74GB | Peak: 0.74GB | Commit: 1.04GB | Faults: 254646 | Machine: 34.05GB
panic(thread 32628): Stack overflow
oh no: Bun has crashed. This indicates a bug in Bun, not your code.
https://bun.report/1.3.8/w_1b64edcbkgEugggCCcKERNELBASE.dlluttHCoBNvMemMapStoragex.dll46kuBCoBNvMemMapStoragex.dll6ppw...gg6SA7
panic: Segmentation fault at address 0x1CDB000000
panicked during a panic. Aborting.
在界面上点重试也永远不会成功(下节说明原因)。
关键结论:OCR 引擎本身是好的,崩的是 smoke 自检
用同一个 dim.exe、同一个官方载荷,只要给一个正常的用户 profile,两个 fixture 全部通过:
known-text.png ok=true pageCount=1 2534–5602 ms text="DIMCODE OCR 12345678"
mixed.pdf ok=true pageCount=2 2716–3158 ms text="MIXED PAGE 1 DIMCODE OCR SCANNED PAGE 987654"
也就是说 light_ocr_node.node + onnxruntime WebGPU 在这台机器上工作正常,问题只出在校验时的环境构造。
根因
FeatureRuntimeManager.smoke(app.asar → packages/electron-main)为了隔离,会把 OCR 自检跑在一个全新临时 profile 下:
const smokeDir = await realpath(await mkdtemp(join(tmpdir(), "dimcode-feature-smoke-")));
const fakeHome = join(smokeDir, "home");
const env = {
...this.options.env(),
HOME: fakeHome,
USERPROFILE: fakeHome,
XDG_CONFIG_HOME: join(fakeHome, "config"),
XDG_CACHE_HOME: join(fakeHome, "cache"),
DIMCODE_HOME: join(fakeHome, "dimcode"),
DOTNET_CLI_HOME: fakeHome,
OFFICECLI_NO_AUTO_RESIDENT: "1",
DIMCODE_LIGHT_OCR_RUNTIME_ROOT: installRoot,
};
// dim.exe ocr recognize ... (timeout 180s)
问题在于:NVIDIA 驱动的 DirectX 着色器缓存路径跟着 USERPROFILE 走,而不是 %LOCALAPPDATA% 环境变量。
重定向 USERPROFILE 之后,驱动把 DXCache 建在临时目录里:
<TEMP>\dimcode-feature-smoke-XXXX\home\
appdata\local\NVIDIA\DXCache\ <-- 空的
appdata\locallow\NVIDIA\DXCache\ <-- 空的
于是 onnxruntime 的 WebGPU execution provider 必须从零开始做 DXC 着色器编译,在这台机器的驱动上会在 NvMemMapStoragex.dll(nvhdcsi.inf 组件)里递归到线程栈溢出,进程以 0xC0000005 终止,Bun 打印上面的 panic。
这解释了两个反常指标:
Sys: 28156ms / User: 3765ms —— 34 秒里 28 秒是内核态,几乎全耗在驱动/页错误上(Faults: 254646);
- 崩溃栈帧在
KERNELBASE.dll 与 NvMemMapStoragex.dll 之间反复出现 —— 典型的驱动内深递归。
因为 mkdtemp 每次都生成新的随机目录、结束后还 rm -rf,着色器缓存每次都是冷的,所以重试不会变好,只会重复崩溃。
复现与定位实验(本机实测)
| 场景 |
结果 |
| App 自检(重定向 profile,冷 DXCache) |
崩溃:34190 / 50007 / 51793 / 83471 ms,栈帧与上报一致 |
重定向 profile + 把 %LOCALAPPDATA%\NVIDIA\DXCache 预拷进 fake home(仅此一项,脚本见下节对照组) |
通过,2788 ms |
| 真实 profile(DXCache 已预热) |
通过,2534–5602 ms |
第三行证明:唯一变量就是那张空的 DXCache。(上面两行都是用下一节的脚本实跑的结果。)
顺带一个有用的事实:崩溃并不是彻底的死局。同一次 smoke 里,第 1 个 fixture 崩溃后(那次已经往缓存里写了条目),第 2 个 fixture(mixed.pdf)在同一个 fake home 下直接通过。也就是崩溃后的「第二次」是能成功的。
最小复现
不需要装 DimAgent,只要有 dim.exe 和一份 light-ocr 运行时就够了:
$cli = "$env:LOCALAPPDATA\Programs\DimAgent\resources\runtime\cli\dim.exe"
$fixtures = "$env:LOCALAPPDATA\Programs\DimAgent\resources\feature-runtime-fixtures"
# light-ocr 载荷解包后的根目录(含 manifest.json / engine / model)
$ocrRoot = "<path-to>\light-ocr"
$smoke = Join-Path $env:TEMP ("dim-ocr-repro-" + [guid]::NewGuid().ToString("N").Substring(0,6))
$fakeHome = Join-Path $smoke "home"
New-Item -ItemType Directory -Force -Path $fakeHome | Out-Null
# 等价于 FeatureRuntimeManager.smoke 的环境
[Environment]::SetEnvironmentVariable("HOME", $fakeHome, "Process")
[Environment]::SetEnvironmentVariable("USERPROFILE", $fakeHome, "Process")
[Environment]::SetEnvironmentVariable("XDG_CONFIG_HOME", (Join-Path $fakeHome "config"), "Process")
[Environment]::SetEnvironmentVariable("XDG_CACHE_HOME", (Join-Path $fakeHome "cache"), "Process")
[Environment]::SetEnvironmentVariable("DIMCODE_HOME", (Join-Path $fakeHome "dimcode"),"Process")
[Environment]::SetEnvironmentVariable("DIMCODE_LIGHT_OCR_RUNTIME_ROOT", $ocrRoot, "Process")
& $cli ocr recognize (Join-Path $fixtures "known-text.png") `
--provider light-ocr-local --model ppocrv6-small `
--out-dir (Join-Path $smoke "known-text.png") --json
# => panic: Stack overflow / Segmentation fault
# 对照组:先把 DXCache 拷进 fake home,再跑一次
$dst = Join-Path $fakeHome "appdata\local\NVIDIA\DXCache"
New-Item -ItemType Directory -Force -Path $dst | Out-Null
Copy-Item "$env:LOCALAPPDATA\NVIDIA\DXCache\*" $dst -Force
& $cli ocr recognize (Join-Path $fixtures "known-text.png") `
--provider light-ocr-local --model ppocrv6-small `
--out-dir (Join-Path $smoke "known-text.png") --json
# => {"ok":true,...,"text":"DIMCODE OCR\n12345678"} ~2.5s
建议的修法
按代价从低到高:
- 崩溃后自动重试一次。实测崩溃那次已经把部分着色器写进缓存,紧接的第二次就能通过(同一次 smoke 的
mixed.pdf 即为例证)。这是最小改动,也能顺带覆盖第一种真实用户场景。
- OCR 自检不要重定向
USERPROFILE。只隔离 DIMCODE_HOME / XDG_* 就足够达到「不污染用户配置」的目的;或者把 LOCALAPPDATA(以及驱动实际使用的 <USERPROFILE>\AppData\Local)显式指向真实路径,让驱动复用已预热的 DXCache。
- 让自检走 CPU execution provider。
runtime-descriptor.json 的 autoPolicy 目前是 ["webgpu","cpu"];如果校验用 CPU、正常运行仍用 WebGPU,就完全不会碰 DXC 编译,也就不会触发这个驱动崩溃。这样还能顺便把校验时间从几十秒降到几秒。
- 如果希望保留 WebGPU 自检,可在跑之前做一次热机(先编译一次再判定),避免把「冷缓存首次编译」当成失败。
补充:这个崩溃不限于自检路径。任何让 USERPROFILE\AppData\Local\NVIDIA\DXCache 变空的场景(换驱动、清理工具、全新用户)都可能让用户第一次真正使用本地 OCR 时同样崩掉;所以第 2/3 条对正常使用路径也有价值。
我这边的临时规避
作为用户侧临时手段,我把官方载荷(SHA-256 与 feature-runtime-catalog.json 一致、light_ocr_node.node 签名有效)直接装到 App 期望的目录并写入校验记录:
%APPDATA%\DimAgent\feature-runtimes\light-ocr\0.5.7-ppocrv6-small-native-20260719.1.desktop.1\win32-x64\
%APPDATA%\DimAgent\feature-runtimes\light-ocr\0.5.7-ppocrv6-small-native-20260719.1.desktop.1\win32-x64.json
App 启动时 applyManagedInstallation 只做 validateRecord + validatePayload(不跑 smoke),因此能被采纳、功能变可用。
但这显然只是绕过,只要用户点一次「重试/安装」就会再次回滚。希望官方从上面 1–4 里挑一个修掉。
环境
resources/runtime/cli/dim.exe)0.5.7-ppocrv6-small-native-20260719.1.desktop.1(win32-x64)e3f3a14cfda8e2e9a27679d0ffc9e3096ada9102882c5ab9a6c9032a17fefcb9现象
设置 → 功能 里的 Light OCR 一直显示「失败」,无法安装/启用。失败信息为:
在界面上点重试也永远不会成功(下节说明原因)。
关键结论:OCR 引擎本身是好的,崩的是 smoke 自检
用同一个
dim.exe、同一个官方载荷,只要给一个正常的用户 profile,两个 fixture 全部通过:也就是说
light_ocr_node.node+ onnxruntime WebGPU 在这台机器上工作正常,问题只出在校验时的环境构造。根因
FeatureRuntimeManager.smoke(app.asar → packages/electron-main)为了隔离,会把 OCR 自检跑在一个全新临时 profile 下:问题在于:NVIDIA 驱动的 DirectX 着色器缓存路径跟着
USERPROFILE走,而不是%LOCALAPPDATA%环境变量。重定向
USERPROFILE之后,驱动把 DXCache 建在临时目录里:于是 onnxruntime 的 WebGPU execution provider 必须从零开始做 DXC 着色器编译,在这台机器的驱动上会在
NvMemMapStoragex.dll(nvhdcsi.inf组件)里递归到线程栈溢出,进程以0xC0000005终止,Bun 打印上面的 panic。这解释了两个反常指标:
Sys: 28156ms / User: 3765ms—— 34 秒里 28 秒是内核态,几乎全耗在驱动/页错误上(Faults: 254646);KERNELBASE.dll与NvMemMapStoragex.dll之间反复出现 —— 典型的驱动内深递归。因为
mkdtemp每次都生成新的随机目录、结束后还rm -rf,着色器缓存每次都是冷的,所以重试不会变好,只会重复崩溃。复现与定位实验(本机实测)
%LOCALAPPDATA%\NVIDIA\DXCache预拷进 fake home(仅此一项,脚本见下节对照组)第三行证明:唯一变量就是那张空的 DXCache。(上面两行都是用下一节的脚本实跑的结果。)
顺带一个有用的事实:崩溃并不是彻底的死局。同一次 smoke 里,第 1 个 fixture 崩溃后(那次已经往缓存里写了条目),第 2 个 fixture(
mixed.pdf)在同一个 fake home 下直接通过。也就是崩溃后的「第二次」是能成功的。最小复现
不需要装 DimAgent,只要有
dim.exe和一份 light-ocr 运行时就够了:建议的修法
按代价从低到高:
mixed.pdf即为例证)。这是最小改动,也能顺带覆盖第一种真实用户场景。USERPROFILE。只隔离DIMCODE_HOME/XDG_*就足够达到「不污染用户配置」的目的;或者把LOCALAPPDATA(以及驱动实际使用的<USERPROFILE>\AppData\Local)显式指向真实路径,让驱动复用已预热的 DXCache。runtime-descriptor.json的autoPolicy目前是["webgpu","cpu"];如果校验用 CPU、正常运行仍用 WebGPU,就完全不会碰 DXC 编译,也就不会触发这个驱动崩溃。这样还能顺便把校验时间从几十秒降到几秒。补充:这个崩溃不限于自检路径。任何让
USERPROFILE\AppData\Local\NVIDIA\DXCache变空的场景(换驱动、清理工具、全新用户)都可能让用户第一次真正使用本地 OCR 时同样崩掉;所以第 2/3 条对正常使用路径也有价值。我这边的临时规避
作为用户侧临时手段,我把官方载荷(SHA-256 与
feature-runtime-catalog.json一致、light_ocr_node.node签名有效)直接装到 App 期望的目录并写入校验记录:App 启动时
applyManagedInstallation只做validateRecord+validatePayload(不跑 smoke),因此能被采纳、功能变可用。但这显然只是绕过,只要用户点一次「重试/安装」就会再次回滚。希望官方从上面 1–4 里挑一个修掉。