问题
两件事:
- Hermes 桌面整体操作卡慢,尤其设置菜单和编辑模型列表巨慢。
- 模型列表里有很多压根不想看到的模型,希望能统一过滤,并且过滤落在更底层,不要蔓延到数据层和 UI 层。
结论(2026-10-25 复盘)
两件事是两个独立的根因,已分别解决,用户验收:UI 速度非常快,问题完全解决。
| 症状 | 根因 | 处置 | 状态 |
| 整机卡、桌面交互迟滞、设置菜单/模型列表巨慢 | Electron 未走硬件渲染:NVIDIA EGL 初始化失败后一进软件渲染(SwiftShader),且落下粘滞标记文件,之后每次启动都继续软件渲染 | 关闭 NVIDIA SwiftShader 回退,删除粘滞标记;渲染进程恢复 GPU 加速 | 已解决 |
| 模型列表里不想看的模型无法在底层统一过滤 | 后端只有 model_catalog.excluded_providers(provider 粒度),全仓没有模型级排除键;桌面端现有的可见性开关只写浏览器 localStorage,仅作用于 UI 层 |
结论:不改源码做不到模型级底层过滤;provider 级可配,模型级需落在 inventory.py 那条唯一链路上 |
已查实(能力边界) |
一、卡慢的根因与修法
根因链
Electron 启动
→ GPU 初始化时 NVIDIA EGL 加载失败
→ 自动回退 SwiftShader(纯 CPU 软件渲染)并写出粘滞标记
→ 之后每次启动都直接走软件渲染
→ 渲染主循环把多核 CPU 吃满(实测 300~650% CPU)
→ 整机负载飙升(3 分钟内 5.8 → 11.5),所有 UI 操作排队
→ 设置菜单、模型列表这类重列表页最明显
关键判据:进程参数里能看到 --use-angle=swiftshader,且日志出现 NVIDIA EGL fallback enabled,没有独立的 gpu-process 正常工作。这不是「列表渲染慢」的前端问题——CPU 被软件渲染吃掉了,什么页面都慢。
修法(三步)
- 备份环境变量文件(改名带时间戳留档)
- 写入
HERMES_DESKTOP_NVIDIA_SWIFTSHADER=0,禁用 NVIDIA SwiftShader 回退 - 删除粘滞标记文件
nvidia-egl-fallback.json——不删它,开关改了也回不到硬件渲染
验证判据(重启后):启动日志里 NVIDIA EGL fallback enabled 那行消失;进程参数里不再有 use-angle=swiftshader;CPU 从数百个百分点回落到正常量级。
修复后复测(2026-10-25 12:34,用户已在用)
renderer 16.4% CPU RSS 953 MB ← 正常量级(此前 300~650%)
gpu-process 7.7% CPU RSS 177 MB ← 独立 GPU 进程正常工作
zygote 0.0% CPU ← 之前的空转死循环已消失
主进程 1.4% CPU
系统负载 3.27 / 2.80 / 3.58 ← 从 11.5 回落
粘滞标记 nvidia-egl-fallback.json 不存在
二、模型过滤的能力边界
链路(唯一数据源)
config.yaml: model_catalog.excluded_providers
→ hermes_cli/inventory.py:56 load_picker_context()
→ build_models_payload(excluded_providers=...)
→ list_authenticated_providers(...)
→ 桌面 / TUI / CLI / gateway 全部消费方
这条链是所有模型列表的唯一数据源,在这层过滤才谈得上「不蔓延到 UI 层」。现有配置只能到 provider 粒度:
model_catalog:
excluded_providers:
- opencode-go # 整个 provider 从所有入口消失
- ds-buy
已验证的事实(grep 源码)
| 检查项 | 结果 |
| 消费点 | inventory.py:56 → build_models_payload → 桌面/TUI/CLI/gateway,一次配置四处生效 |
| provider 级排除 | 已实现,匹配 provider slug 或别名(大小写不敏感) |
| 模型级排除 | 不存在:全仓无 excluded_models 或同义键 |
| 桌面现有可见性开关 | 写浏览器 localStorage(hermes.desktop.visible-models),只作用于桌面 UI;CLI/TUI/gateway/API 都不认 |
因此:某个 provider 整块不要 → 现在就能配;只想去掉某些模型 → 不改源码没有配置项。要模型级底层过滤,必须给 model_catalog 加一个模型级排除键,挂在 inventory.py 那条唯一链路上,前端一行不动。
附带一条桌面端既有行为:新模型出现时会走「策展默认规则」而不是默认隐藏(known-models 快照机制)——意味着纯 UI 层的隐藏列表不会自动覆盖新上架的模型,每来一批新模型都要手工再勾一次。这是把过滤放到数据层的另一个理由。
三、教训(沉淀)
- 先分离层次再动手:整机慢 ≠ 某个页面慢。看 CPU 归属(renderer / gpu-process / zygote)比看 UI 帧率更快定位;
zygote空转与swiftshader回退是两个不同故障,别混成一个。 - 粘滞标记是隐藏关卡:只改开关不删标记文件,回退状态会一直生效。
- 配置项存在 ≠ 粒度够用:
excluded_providers的真实粒度就是 provider,读源码确认「没有模型级键」比猜更快。 - 过滤要选在唯一数据源上:同一份模型清单有四个消费方,只有后端
build_models_payload那一层能一次覆盖全部。