Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions docs/releases/2.0.2-Beta.1-release-notes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# OpenLess 2.0.2-Beta.1+build.20261017

本次为 **Beta 测试版**,不替换当前正式版 2.0.1。比较范围:上一公开 Beta **`v2.0.0-Beta.7-tauri` → `v2.0.2-Beta.1-tauri`**,包含期间正式版的打包改进及本轮胶囊、本地识别更新。

## 功能更新

- **多屏胶囊可自行选择**:在「设置 → 录音与输入 → 多屏胶囊」选择「鼠标所在屏幕」或「所有屏幕显示」。支持 macOS、Windows,默认跟随鼠标;设置会保存,恢复配置后也会应用。所有屏幕模式使用额外窗口与图形资源,只有显式选择后才创建镜像。
- PR:[ #1222](https://github.com/Open-Less/openless/pull/1222);提交者与代码署名:[@appergb](https://github.com/appergb)。

## Bug 修复

- **模型加载时点停止迟迟不响应**:本地模型的加载与麦克风状态解耦,麦克风就绪即可录音;点击停止先释放麦克风并进入转写,不再等模型加载完才处理停止。取消后不回放已取消音频,尚未执行的加载不再启动;加载失败会明确反馈,不伪造识别成功。
- PR:[ #1222](https://github.com/Open-Less/openless/pull/1222);提交者与代码署名:[@appergb](https://github.com/appergb)。
- **本地语音资源占用突增**:限制 TextStream 的 MLX 空闲缓存和内存工作集指导值;SenseVoice 固定单 CPU 推理线程,避免忙闲预算变化触发整模型重载和双份驻留。本地推理仍会占用模型所需内存,CPU 预算为可协作的平均约束,不是瞬时硬限制。
- PR:[ #1222](https://github.com/Open-Less/openless/pull/1222);提交者与代码署名:[@appergb](https://github.com/appergb)。
- **多屏显示与切换**:默认不再在每个屏幕重复显示胶囊;选择所有屏幕后仍只使用同一识别会话。镜像不显示学习建议或落字失败卡片;卡片始终由主胶囊处理。切回单屏、隐藏胶囊或停止后,镜像隐藏并停止动画,不再接收实时胶囊帧。
- PR:[ #1222](https://github.com/Open-Less/openless/pull/1222);提交者与代码署名:[@appergb](https://github.com/appergb)。

## 架构改动

- **本地加载与校验生命周期**:通过宿主任务调度异步加载实验模型;TextStream 对文件身份和元数据未变的已校验权重使用进程内缓存。首次使用、文件变化或新进程仍校验完整 SHA-256,未知 worker 不会被自动覆盖。
- PR:[ #1222](https://github.com/Open-Less/openless/pull/1222);提交者与代码署名:[@appergb](https://github.com/appergb)。
- **有界镜像与配置契约**:两种模式使用同一原生显示策略,处理负坐标与混合 DPI;macOS 镜像采用与主胶囊一致的非激活面板。镜像窗口有限复用,避免每次录音反复创建 WebView;新配置具有旧版默认值、序列化及同步注册校验。
- PR:[ #1222](https://github.com/Open-Less/openless/pull/1222);提交者与代码署名:[@appergb](https://github.com/appergb)。

## 构建与分发改动

- **新 Beta 版本与回归门禁**:应用版本统一为 `2.0.2-Beta.1+build.20261017`,使用新标签,不移动旧 Beta7 或正式版标签。增加两种模式保存/加载、无效值拒绝、模式切换缓存失效,以及 macOS/Windows 设置页的 Chromium/WebKit 回归。
- PR:[ #1222](https://github.com/Open-Less/openless/pull/1222);提交者与代码署名:[@appergb](https://github.com/appergb)。
- **继承已公开正式版的 Windows 打包与安装验证改进**:MSI 数字版本兼容、CI Restart Manager 隔离、成功返回值 3010 处理,以及独立固定版本的安装验证工具。Beta 提供 NSIS,不提供 MSI。
- PR:[#1206](https://github.com/Open-Less/openless/pull/1206)、[#1213](https://github.com/Open-Less/openless/pull/1213)、[#1215](https://github.com/Open-Less/openless/pull/1215);提交者与代码署名:[@appergb](https://github.com/appergb)。这些 PR 已合入 beta,不是本轮新写的应用功能。

## PR 与参与者

| PR | 内容 | PR 提交者 | 代码署名 |
| --- | --- | --- | --- |
| [#1222](https://github.com/Open-Less/openless/pull/1222) | 多屏模式、本地识别修复、回归与本 Beta 版本准备 | [@appergb](https://github.com/appergb) | [@appergb](https://github.com/appergb) |
| [#1206](https://github.com/Open-Less/openless/pull/1206) | 继承的日期 MSI 与定向草稿打包 | [@appergb](https://github.com/appergb) | [@appergb](https://github.com/appergb) |
| [#1213](https://github.com/Open-Less/openless/pull/1213) | 继承的 Windows 安装验证环境隔离 | [@appergb](https://github.com/appergb) | [@appergb](https://github.com/appergb) |
| [#1215](https://github.com/Open-Less/openless/pull/1215) | 继承的 MSI 3010 成功码与验证工具固定 | [@appergb](https://github.com/appergb) | [@appergb](https://github.com/appergb) |

本轮问题反馈与双模式需求来自用户实际使用反馈;未擅自公开反馈者身份。源码复查及验证由提交工作流完成,GitHub 审查、讨论、合并的具体记录以各 PR 页面为准,不将提交者自查冒称独立批准。继承的三个打包 PR 已由 @appergb 合并;本轮 #1222 的合并者和最终检查在公开前核对。

## 验证说明

- 本地 Core 1211 项通过,新增偏好回归 3/3;macOS 原生 583 项通过,5 项需外部模型/安装条件的测试明确跳过;浏览器 Chromium/WebKit 完整 116/116。
- 同一 ASR 源码此前以已有模型及合成语音验证:TextStream 30.46 秒样本转写相同,worker 峰值 RSS 从约 1674 MiB 降至 1341 MiB;此样本没有证明 CPU 瞬时峰值下降。SenseVoice 单线程会牺牲部分解码速度,以降低争用并避免反复重载。
- 默认单屏、显式多屏、鼠标屏幕选择、DPI、配置保存和模式切换均有回归。自动化检查不等于所有设备和真双屏交互均已验收;模型与宿主仍可能有短时资源突发,欢迎通过本 Beta 反馈实际表现。
- 包校验和及更新签名在公开前核对,附 `SHA256SUMS`。详细定位和测试边界见[修复记录](https://github.com/Open-Less/openless/blob/beta/docs/reviews/2026-10-16/local-asr-capsule-performance.md)。

## 下载与升级

- **macOS**:macOS 14 Sonoma 或更新版本;Apple Silicon 选 `aarch64.dmg`,Intel 选 `x64.dmg`。当前包使用 ad-hoc 签名、未做 Apple 公证;拖入 Applications 后,按现有分发方式执行:

```sh
xattr -cr /Applications/OpenLess.app
```

- **Windows**:x64 NSIS 安装包,未做发布者证书签名。
- **Android**:四种 ABI 的签名 APK;多屏胶囊选项仅用于桌面 macOS/Windows。
- **Linux**:本次不附带 Linux 安装包。
- Beta 不推送给 Stable 更新源;[当前正式版 2.0.1](https://github.com/Open-Less/openless/releases/tag/v2.0.1-tauri)保持不变。
63 changes: 63 additions & 0 deletions docs/reviews/2026-10-16/local-asr-capsule-performance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# 本地识别资源占用与多屏胶囊

## 范围

基线 beta `96278f40`。处理更新后多屏同时显示胶囊、macOS 本地识别启动/停止等待和资源突增;不更改用户模型开关、凭据、录音、权限或已安装 App,不改已公开版本及标签。当前机器已有 SenseVoice 与 TextStream 权重,Gemma 不具备本次真实模型测试条件。

## 原因与修正

### 停止响应

普通听写在麦克风启动后仍 await `buffered.attach()`,本地模型加载/校验未完成时,Core 公开状态仍为 Starting。`stop_dictation_session_with_options` 会等待该状态结束,导致用户已点停止但麦克风不能及时释放、思考状态也迟迟不出现。SenseVoice 合成音频测试中冷加载约 8–10 秒,能解释这类等待;它不是关闭按钮没有绑定。

只对实验本地 ASR 使用宿主 TaskSpawner 后台 attach。麦克风已就绪即可进入 Recording;停止先释放 capture 并进入正常识别收尾,结果仍等待真实模型输出,不伪造完成。模型启动失败通过 RecordingFault 反馈,finalizing 后由 finish 返回原始错误;取消前尚未调度的加载不执行,加载中取消后的迟到 session 不回放音频并清理。云端管线和先录音后识别的稳定模式不改变。

### CPU、内存与加载

- TextStream 每次启动都会重新读取并 SHA-256 校验约 1.9 GB 固定权重。新增仅存在当前进程的校验缓存,文件目录、设备、inode、大小及 mtime/ctime(纳秒)未变才复用;首次、替换、修改或新进程仍全量校验,校验前后发生变化不缓存。没有写入可跨进程复用的信任标记。
- MLX 默认内存指导上限为推荐工作集的 1.5 倍,空闲缓存默认也可增长到该上限。现在显式设置工作集指导值为物理内存的三分之一、推荐工作集与 4 GiB 的最小值;空闲缓存最多 128 MiB,并在加载/识别片段/最终化后释放不再使用的缓存。它不是 OS 级硬 RSS 限额,活动模型权重和 KV 状态不会被强删。
- 原 CPU 总预算可能达到 5–7 个逻辑核,线程参数最多 2 不等于全部 native 辅助线程总用量最多 2。现在协作式平均预算另加 2 个逻辑核上限,保留 utility QoS 和可取消让步;单次不可分割 native 调用仍可能短时突发。
- SenseVoice 先前按采样到的空闲 CPU 调整线程,忙闲变化可能重建整个 ONNX 模型;创建新模型时旧模型还在 slot 内,造成双份驻留峰值。SenseVoice 固定单 CPU 推理线程,已加载实例不因预算变化重建;必须替换的实例在构造新模型前释放。单线程优先降低争用,不承诺更快的批量推理。
- 补充无文本的模型加载、包校验缓存命中、ready 和解码阶段计时。
- 受管 TextStream worker 自动升级同时识别已知旧脚本/环境指纹,保留 Python 环境和权重;未知或修改过的脚本不覆盖。lease/status 能识别可升级版本,防止旧 worker 在进入 refresh 前被拦截。

### 多屏

后续用户明确要求两种模式自行选择:本轮 Beta 在设置增加 `capsuleDisplayMode`,默认 `cursorScreen`,显式选择 `allScreens` 时才镜像。macOS/Windows 共享策略,macOS 镜像转换为非激活面板并使用目标屏幕逻辑坐标;Windows 保留独立原文 rail。设置同步、恢复和主线程缓存均接线,模式切换使镜像缓存失效。默认值、序列化重载、无效值拒绝、原生模式缓存及两个 OS 的双浏览器控件测试通过。

复查发现频繁销毁/重建 WKWebView 会累积原生资源,因此改为有限槽位复用:不用的窗口隐藏并投递 Idle 停止动画,实时 capsule 状态只发往可见镜像;不为每块屏幕创建额外识别会话。以下是最初单屏修复的历史说明,新 Beta 已不再强制用户只能单屏。

Windows #1183 曾主动增加“主屏固定胶囊 + 其他屏幕镜像”,因此两个屏幕同时显示是该版本的明确行为,不是识别引擎创建两份 UI。现在只显示鼠标所在屏幕上的一个 capsule / transcript rail,兼容路径隐藏已有镜像;鼠标不可读时回退主屏。混合 DPI、负坐标屏幕和卡片模式使用同一定位依据。

macOS 镜像创建原本就被 cfg 排除,继续保留现有鼠标屏幕定位;不把 Windows 镜像根因冒称为 macOS 双胶囊已复现。本机没有完成 Windows 真双屏 UI 操作,定位与重复窗口策略由纯几何回归验证,Windows 编译需云端门禁。

## 验证

- Core 初轮全库:1210 passed / 1 外部依赖测试 ignored;后增“取消已排队的本地加载”后,最终受影响 dictation_engine 回归 34/34。覆盖三类实验模型启动尚未完成时的停止、取消、音频完整性、启动失败、云端与稳定模式原有语义。
- 正常原生 macOS 全库:582 passed / 5 明确的模型/安装依赖 ignored。SenseVoice 与固定权重 TextStream 校验两项 ignored 测试另以已有模型、合成语音实际执行通过,不下载模型、不使用用户录音。
- Core Clippy `--lib --tests -- -D warnings` 通过。改动后的 Python worker 13 项测试通过,Black/Ruff 通过,资源策略 helper 的 mypy strict 通过;既有 lifecycle/retirement/capsule replay 契约通过。
- 原生包缓存回归覆盖同大小文件替换、删除、校验中修改和未知 worker 拒绝;旧 worker receipt 升级保留解释器。
- 胶囊多屏纯逻辑 9/9,覆盖多屏不创建镜像、负坐标鼠标选择、DPI、边界与拓扑变化。

## 合成音频对比(本机开发验证,不是所有设备性能保证)

TextStream 使用同一 30.46 秒英语合成音频、相同已有权重和平均 2 核预算,直接启动生产 worker;100ms 采样 worker RSS/CPU,输入由独立发送线程供给,不阻断采样。未读取用户音频。

| 指标 | 修正前 | 修正后 |
| --- | --- | --- |
| worker 峰值 RSS | 1674.14 MiB | 1340.75 MiB |
| 首个草稿(含启动) | 2.18s | 1.65s |
| 最终结果(含启动) | 9.21s | 8.76s |
| 合成音频转写 | 完整 | 与修正前相同 |

RSS 降约 20%,采样 CPU 峰值约 88.5% / 90.3%,这次样本不支持“CPU 峰值已下降”的结论;平均预算与线程约束由计时回归验证。

SenseVoice 单线程实测:6.092 秒合成音频识别正确,冷加载 9.72s,随后 warm prepare 112ms,解码约 1.03s。此前两线程一次约 544ms;单线程降低争用会增加这段解码时间,不能包装成普遍提速。

TextStream 包校验在 debug 未优化 SHA 实测 cold 53.07s、warm 约 50µs,仅说明避免重复全文件读校验有效;不将 debug 耗时当作用户 Release 包的真实耗时。

## 本机与磁盘边界

磁盘此前开发构建缓存已清理,当前可用空间约 79 GiB;本轮只用一个共享 validation target、2 个构建任务、关闭 incremental/debug info,没有重建多个工作区的 target。未删除未经确认的系统快照。本轮验证结束后清理不再需要的临时构建产物,保留小型日志与对比结果用于复核。

当前只是源码修复与验证,不宣称已安装、已重新发布或所有设备问题已消失。
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,13 @@ pub const PREFERENCE_FIELDS: &[PreferenceField] = &[
shape: PreferenceShape::Text,
reason: "portable_setting",
},
PreferenceField {
rust_name: "capsule_display_mode",
key: "capsuleDisplayMode",
class: PreferenceClass::Portable,
shape: PreferenceShape::Text,
reason: "portable_setting",
},
PreferenceField {
rust_name: "capsule_transcript_enabled",
key: "capsuleTranscriptEnabled",
Expand Down Expand Up @@ -900,6 +907,7 @@ pub fn validate_preference_value(field: &PreferenceField, value: &Value) -> Docu
let choices: &[&str] = match field.key {
"defaultMode" => &["raw", "light", "structured", "formal"],
"capsuleStyle" => &["siri", "classic", "typeless"],
"capsuleDisplayMode" => &["cursorScreen", "allScreens"],
"pipelineMode" => &["traditional", "multimodal"],
"chineseScriptPreference" => &["auto", "simplified", "traditional"],
"outputLanguagePreference" => &["auto", "zhCn", "zhTw", "en", "ja", "ko"],
Expand Down
Loading
Loading