跳转至

归档说明(2026-07-12): 在根目录清理中从 .planning/ 移入此处。 下文中的内部 .planning/ / specs/ 路径均为历史遗留——这些目录树已被删除;请参阅 git 历史。

阶段 4:自适应与专业引擎(spike-first)——研究

许可证更正(2026-10-03)

本更正取代下文中原有的商业兼容性结论。 上游 OmniVoice 模型卡 将 Apache-2.0 代码与 CC-BY-NC 预训练权重区分开来。 GGUF 模型卡 现在将其转换后的权重列为 CC-BY-NC-4.0。 歌声模型卡 仍宣称 Apache-2.0,并将 OmniVoice 标识为其基础;该标注并不能解决上游条款相互冲突的问题。歌声权重的商业许可仍未得到验证。Tokenizer/组件条款也需要单独审查;请参阅 LICENSE-NOTICE.md。

这些历史性的工程决策并不代表任何模型已获批用于 Pro。模型权利必须与运行时代码权利分开记录;除非已获得必要的权利,否则未解决或非商业用途的权重不得纳入 Pro。所需的验收和后端强制执行工作已在 #2587 中跟踪;本更正并不声称这些控制措施已经实施。

研究时间: 2026-05-18 领域: TTS 引擎集成 —— 量化 GGUF 运行时 + 歌声变体,两者均源自同一个 k2-fsa/OmniVoice 血统,该模型已作为 VoiceStudio 的默认克隆引擎发布。 置信度: 模型身份和运行时要求为高。原有的许可证和商业 GO/NO-GO 结论已被上述更正取代。性能/延迟数据为中(没有公开基准)。启发式歌声/语音分段策略(SING-03——使用现有工具包可行,但未经基准测试)为中。


摘要

两个 spike URL 都是真实、有效,且正是预期中的工件。"VoiceStudio" 这个名字在真实环境中并没有被重载占用——Serveurperso/OmniVoice-GGUF 和 ModelsLab/omnivoice-singing 都是 k2-fsa/OmniVoice 的直接后代(即 VoiceStudio 已作为默认克隆引擎发布的同一个上游模型)。它们的共同血统并不能确立商业许可;请参阅上面的许可证更正。两者都使用相同的 Higgs Audio v2 编解码器(24 kHz 单声道)、相同的 Qwen3-0.6B 语言模型骨干,以及相同的整体架构——它们唯一的不同在于 (a) 量化 + 运行时(GGUF/omnivoice.cpp)和 (b) 微调数据集 + 情感/歌声控制标签(ModelsLab)。

一旦这一点得到确认,整个框架的定位就会改变。 SPIKE-01 不是"引入新引擎"——而是"为 VoiceStudio 内已有的引擎提供一个量化运行时变体,可通过硬件探测选择"。SPIKE-02 不是"引入新的引擎架构"——而是"为同一个 VoiceStudio 模型提供带歌声模式标签的领域自适应微调版本,可通过现有 VoiceStudioBackend API 表面、使用不同的 from_pretrained ID 调用"。

主要建议:

  • SPIKE-01(OmniVoice-GGUF):技术 GO,前提是 Phase 4 内部对 Apple Silicon 进行 buildmetal.sh 冒烟测试(omnivoice.cpp/README.md 中没有可见的已发布 Metal 构建脚本;只记录了 Vulkan 和 CPU)。集成形态是 SubprocessBackend 包装 omnivoice-tts C++ CLI,量化等级由 detect_capabilities() 硬件探测选择。
  • SPIKE-02(omnivoice-singing):GO,但范围缩减,将其视为现有 VoiceStudioBackend 的第二个 from_pretrained ID,而不是新的后端类——它使用同一个 omnivoice PyPI 库、同一个 transformers 流水线、同一个模型接口。配音流水线的"歌声模式"开关(SING-02)和 Demucs 人声分轨路由(SING-03)仍然是实际工作,但它们是流水线集成而非引擎集成。净效果:5 个 SING-* 需求保持在范围内;SING-01 的框架得到简化。

Phase 2 依赖: 两个引擎都构建在 Phase 2 的 SubprocessBackend 原语之上。在 Phase 2 的 RESEARCH.md 存在并确认 subprocess + venv + mp.get_context("spawn") + HF_HOME 继承契约之前,Phase 4 无法最终确定 PLAN.md。请将其视为规划门禁,而不是被 Phase 2 阻塞的研究——研究可以以 SubprocessBackend 作为稳定契约继续进行(ROADMAP 和 SUMMARY 都定义了它)。在 Phase 2 RESEARCH 落地之前,PLAN.md 不能引用其内部细节。


架构职责映射

能力 主层级 次层级 理由
GGUF 模型推理 API/Backend(subprocess) OS/原生二进制(omnivoice-tts) 围绕 C++ CLI 的 subprocess 包装;VoiceStudio 主进程内不进行 Python 级推理工作
硬件探测 + 量化等级选择 API/Backend Frontend(设置 UI 界面) detect_capabilities() 扩展位于 backend/services/;UI 仅渲染结果
量化覆盖 UI Frontend API/Backend(持久化) 面向用户的一键开关;后端将选择存入 SQLite 设置
歌声模型推理 API/Backend(通过 omnivoice pip 库在进程内运行) — 现有 VoiceStudioBackend 上可直接替换的 from_pretrained ID;无新增层级
配音流水线"歌声模式"开关 API/Backend(dub_pipeline.py 扩展) Frontend(配音任务 UI) 路由逻辑位于配音服务中;UI 暴露该开关
语音与歌声片段自动检测 API/Backend(信号处理启发式) — 基于 Demucs 人声分轨的音高稳定性 + 能量分析;纯后端
决策文档 仓库元数据 — .planning/decisions/*.md ADR——两个层级都不拥有这些

标准技术栈

核心——SPIKE-01(OmniVoice-GGUF)——GO 路径

| 库 / 工件 | 版本 | 用途 | 为何视为标准 | | omnivoice.cpp(从源码构建的二进制) | 集成时的 master 最新提交,按 commit SHA 固定 | GGUF 推理运行时 — VoiceStudio 的 C++17/GGML 移植版本 | 唯一能加载 Serveurperso/OmniVoice-GGUF 量化模型的运行时。尽管采用 GGUF 格式(自定义架构),但兼容性不在 llama.cpp 范围内。MIT 许可,由发布这些量化模型的同一作者维护。 | | Serveurperso/OmniVoice-GGUF 模型文件 | 在 quant_map.json 中按 commit SHA 固定 | 预量化权重 | 4 种量化 × 2 个文件(基础 + 分词器):Q4_K_M(约 659 MB VRAM)、Q8_0(约 945 MB,推荐默认)、BF16(约 1.6 GB)、F32(约 3.2 GB) | | huggingface_hub(已固定) | ≥1.12.x | 量化文件下载 | 复用现有 HF token + 缓存基础设施(依据 Phase 1 的 token 解析器) | | SubprocessBackend(来自 Phase 2) | 不适用 | 引擎隔离原语 | 将 omnivoice-tts CLI 包装为受管理的子进程,并使用每引擎 venv(这里的 venv 仅包含用于下载的 huggingface_hub — 推理二进制是原生程序) |

核心 — SPIKE-02 (omnivoice-singing) — GO 路径

库 / 制品 版本 用途 选型理由
omnivoice(PyPI,已是依赖) 0.1.5(2026-04-28 已验证) VoiceStudio 推理库 — 与 VoiceStudio 已用于 k2-fsa/OmniVoice 的库相同 API 表面完全一致;只是 from_pretrained ID 不同。无新增依赖。
ModelsLab/omnivoice-singing 模型 按 commit SHA 固定 唱歌微调权重 复用 HF token + 缓存,大小与基础模型大致相同
tts_backend.py 中现有的 VoiceStudioBackend 不适用 引擎适配器 新的子类 VoiceStudioSingingBackend(VoiceStudioBackend) 覆盖 from_pretrained ID + display_name,并在 generate() 中输出 [singing] 控制标签。与 Phase 2 交付的 SubprocessBackend 宿主模式相同。
Demucs(已固定) 已固定 人声主干分离 已用于配音流水线;无新增依赖。将人声主干路由到唱歌引擎,乐器主干保持不变。
依赖中已有的 numpy + librosa 等价物 已固定 SING-03 片段检测的基频稳定性 + 能量启发式 无新增依赖;片段边界根据现有 Demucs 输出计算。

备选方案

替代对象 备选方案 权衡
使用 omnivoice.cpp 进行 GGUF 推理 直接使用 llama.cpp 已拒绝。 量化模型使用 omnivoice-lm 架构,而非标准 llama。在原生 llama.cpp 中无法加载;需要维护一个 fork。
使用 omnivoice.cpp 进行 GGUF 推理 ONNX 导出 + onnxruntime 已拒绝。 VoiceStudio 的扩散-LM 混合架构没有已发布的 ONNX 导出路径;需要我们先在 upstream 完成转换工作。超出 v0.3.x 范围。
新建 SingingBackend 类 复用 VoiceStudioBackend,仅更换模型 ID + 控制标签 推荐。 相同的库、相同的架构、相同的编解码器 — 代码重复率可高达 95% 逐字相同。一个覆盖 3 个属性的子类是风险更低的形态。
基于模型的唱歌/说话分类器 基于 Demucs 人声主干的基频稳定性 + 能量启发式 推荐用于 v0.3。 REQUIREMENTS.md 已将模型分类器推迟到 v2。启发式已足够,因为用户可以在配音 UI 中按片段覆盖。
在安装程序中打包 omnivoice-tts 二进制 通过 bootstrap.rs 扩展在安装时构建 v0.3.x 采用打包。 安装时从源码构建会增加一个我们本不需要的 C++ 工具链依赖。各平台二进制(压缩后 3-4 MB)随安装程序一同发布;Phase 3 的镜像级联工作已经为受限网络提供了优雅的 fallback 路径。

安装(唱歌路径无新增 PyPI 依赖;GGUF 只需一次构建/打包步骤):

# Singing variant — zero new deps
# Just adds a model entry; VoiceStudioSingingBackend reuses the existing omnivoice library

# GGUF variant — adds a bundled binary, not a Python dep
# Built once per platform in CI and stored alongside the Tauri installer:
#   bin/omnivoice-tts-{darwin-arm64,darwin-x86_64,windows-x86_64,linux-x86_64,linux-aarch64}
# Quants pulled at first use via huggingface_hub

版本验证(2026-05-18 已验证):

# Confirmed live on PyPI (verified 2026-05-18 via WebFetch):
#   omnivoice 0.1.5 — released 2026-04-28, Apache-2.0
# Already a transitive/direct dependency of VoiceStudio (per tts_backend.py:VoiceStudioBackend)

# Confirmed live on HuggingFace (verified 2026-05-18 via WebFetch):
#   Serveurperso/OmniVoice-GGUF — 10,603 downloads last month, 4 quant variants
#   ModelsLab/omnivoice-singing — historical download count; commercial rights unresolved
#   k2-fsa/OmniVoice (upstream) — separate code/weight terms; arXiv 2604.00688

# Confirmed live on GitHub (verified 2026-05-18 via WebFetch):
#   ServeurpersoCom/omnivoice.cpp — MIT, 38 stars, 59 commits on master, 6 open issues

包合法性审计

包 / 制品 注册源 源码仓库 slopcheck 处置结论
omnivoice PyPI github.com/k2-fsa/OmniVoice 未运行(已是项目依赖) 已批准 — 已在 v0.2.7 中发布
Serveurperso/OmniVoice-GGUF HuggingFace github.com/ServeurpersoCom/omnivoice.cpp 不适用(HF 模型,非代码包) 已批准 [假定 — 已通过 WebFetch 验证模型卡 + 关联的源码仓库;未运行 slopcheck,因为 slopcheck 针对的是 PyPI/npm]
ModelsLab/omnivoice-singing HuggingFace 衍生自 k2-fsa/OmniVoice 不适用(HF 模型) 已批准 [假定 — 已通过 WebFetch 验证;谱系链已端到端确认]
omnivoice.cpp 二进制 GitHub 源码构建 github.com/ServeurpersoCom/omnivoice.cpp 不适用(源码构建,非包) 已批准,但 Phase 4 计划中需包含构建并固定版本的步骤 [假定 — 仓库很小(38 星标、59 次提交、6 个未关闭 issue)— Phase 4 计划必须添加 checkpoint:human-verify 任务,在采用前检查被固定的提交]

已移除的包: 无 被标记为可疑的包: 没有正式标记 —— 但 omnivoice.cpp 应当由规划器插入一个 checkpoint:human-verify,因为它是一个成熟度较低的项目(38 个 star)。验证步骤是:“在打包前检查所固定的提交 SHA 是否存在明显问题;确保该 SHA 与我们构建并测试过的版本一致。”

在本研究会话中,未对 HF/GitHub 工件运行 slopcheck,因为 slopcheck 针对的是代码包注册表(PyPI/npm/crates)——HuggingFace 模型哈希和源码构建需要改用手动来源审查,该审查已通过 WebFetch 获取模型卡及关联仓库完成。

架构模式

系统架构图 — SPIKE-01(GGUF,GO 路径)

                                     ┌──────────────────────────────────────┐
User opens app / first run           │  Settings → Engines → Default        │
        │                            │  ┌──────────────────────────────┐    │
        ▼                            │  │ Auto-selected quant: Q8_0    │    │
┌──────────────────────────┐         │  │ [▼ Override: F32/BF16/Q4_K_M]│    │
│ detect_capabilities()    │ ─────►  │  └──────────────────────────────┘    │
│   - CPU / RAM            │         │  Source of truth: SQLite settings    │
│   - GPU + VRAM (MB)      │         └──────────────────────────────────────┘
│   - compute_class bucket │                          │
│     (CPU/low/mid/high)   │                          │ persisted choice
└──────────────────────────┘                          ▼
        │                            ┌──────────────────────────────────────┐
        │ probe result               │ quant_map.json (shippable, not       │
        ▼                            │ pinned-in-binary):                   │
┌──────────────────────────┐         │   CPU       → Q4_K_M                 │
│ Quant resolver           │ ◄────── │   low-VRAM  → Q4_K_M (≤4 GB VRAM)    │
│ (compute_class → quant)  │         │   mid-VRAM  → Q8_0  (4-12 GB)        │
└──────────────────────────┘         │   high-VRAM → BF16  (≥12 GB)         │
        │                            └──────────────────────────────────────┘
        │ selected quant
        ▼
┌──────────────────────────┐         ┌──────────────────────────────────────┐
│ huggingface_hub.download │ ─────►  │ $HF_HUB_CACHE/                       │
│   Serveurperso/OmniVoice-GGUF      │   omnivoice-base-{Q}.gguf            │
│   pinned-by-SHA          │         │   omnivoice-tokenizer-{Q}.gguf       │
└──────────────────────────┘         └──────────────────────────────────────┘
        │                                          │
        ▼                                          ▼
┌─────────────────────────────────────────────────────────────────────────┐
│ SubprocessBackend (Phase 2 primitive)                                   │
│                                                                         │
│  spawn:  bin/omnivoice-tts-{darwin-arm64|x86_64|win|linux|              │
│                              linux-aarch64}                             │
│          --model    $HF_HUB_CACHE/.../omnivoice-base-{Q}.gguf           │
│          --codec    $HF_HUB_CACHE/.../omnivoice-tokenizer-{Q}.gguf      │
│          --lang     <user lang>                                         │
│          --ref-wav  <voice clone reference, optional>                   │
│          --ref-text <voice clone reference text, optional>              │
│          -o         /tmp/omnivoice-job-<id>.wav                         │
│          < <stdin: prompt text>                                         │
│                                                                         │
│  stdout/stderr → backend.log (filtered for HF token per AUTH-05)        │
│  output file   → read back into torch.Tensor for downstream consumers   │
└─────────────────────────────────────────────────────────────────────────┘

系统架构图 — SPIKE-02(Singing,GO 路径)

┌─────────────────────────────────────────────────────────────────────────┐
│ Dubbing job UI — new "Singing mode" toggle in job-creation form         │
└─────────────────────────────────────────────────────────────────────────┘
                                  │
                                  ▼
┌─────────────────────────────────────────────────────────────────────────┐
│ dub_pipeline.py (existing) — extended with singing routing              │
│                                                                         │
│  Source audio  ──► Demucs (already in pipeline)                         │
│                    ├── vocals stem ────┐                                │
│                    └── instrumental ───┼──► preserved untouched         │
│                                        │    in final mix                │
│                                        ▼                                │
│                    Segment detector (NEW, SING-03 heuristic):           │
│                      pitch stability  + energy                          │
│                      → list[(start, end, kind ∈ {speech, sing})]        │
│                                        │                                │
│                                        ▼                                │
│                    Route segments:                                      │
│                      kind=speech → VoiceStudioBackend (existing default)  │
│                      kind=sing   → VoiceStudioSingingBackend (NEW)        │
│                                        │                                │
│                                        ▼                                │
│                    Re-attach to instrumental stem                       │
│                                        │                                │
│                                        ▼                                │
│                              Final dubbed mix                           │
└─────────────────────────────────────────────────────────────────────────┘

VoiceStudioSingingBackend (NEW class, ≤ 30 lines):
  class VoiceStudioSingingBackend(VoiceStudioBackend):
      id            = "omnivoice-singing"
      display_name  = "VoiceStudio (singing)"
      model_id      = "ModelsLab/omnivoice-singing"
      def generate(self, text, **kw):
          text_with_tag = f"[singing] {text}" if not text.startswith("[") else text
          return super().generate(text_with_tag, **kw)
backend/engines/
├── omnivoice_gguf/
│   ├── __init__.py
│   ├── backend.py            # VoiceStudioGGUFBackend(TTSBackend) — SubprocessBackend host
│   ├── quant_map.json        # compute_class → quant filename (updatable without release)
│   ├── hardware_probe.py     # extends services/gpu_sandbox.py probe
│   └── README.md             # engine card content (license, source URL, hardware notes)
└── omnivoice_singing/
    ├── __init__.py
    ├── backend.py            # VoiceStudioSingingBackend(VoiceStudioBackend) — subclass
    ├── segment_detector.py   # SING-03 pitch + energy heuristic
    └── README.md             # engine card content

backend/services/
├── tts_backend.py            # existing — register both new backends in _REGISTRY
└── dub_pipeline.py           # existing — extend with singing routing (SING-02, SING-03)

bin/                          # bundled in installer (per platform)
├── omnivoice-tts-darwin-arm64
├── omnivoice-tts-darwin-x86_64
├── omnivoice-tts-windows-x86_64.exe
├── omnivoice-tts-linux-x86_64
└── omnivoice-tts-linux-aarch64

.planning/decisions/
├── SPIKE-01-gguf.md          # ADR (this research → human review → ratified)
└── SPIKE-02-singing.md       # ADR

模式一:硬件自适应默认引擎(GGUF-05)

说明: 首次启动时的硬件探测会运行 detect_capabilities(),从 quant_map.json 中选择量化文件,并将 OmniVoice-GGUF 设为克隆默认引擎。用户可以在 设置 → 引擎 → 默认 中覆盖此选择。 适用场景: 仅当硬件探测成功且量化文件能够干净加载时使用。失败模式会回退到已有的 VoiceStudioBackend(进程内 Python 路径),绝不会出现“没有可用引擎”的情况。

# Source: synthesized from Phase 2 SubprocessBackend contract + GGUF-01..05 requirements
# Confidence: HIGH for shape, MEDIUM for default-quant boundaries (no published benchmark)

def select_default_engine() -> str:
    """Run at first launch (and on Settings → Engines → 'Re-probe hardware')."""
    caps = detect_capabilities()  # extends backend/services/gpu_sandbox.py

    # Hardware bucket → quant from quant_map.json (shippable, not pinned in code)
    quant_map = json.load(open(GGUF_DIR / "quant_map.json"))
    quant = quant_map.get(caps.compute_class)  # one of: Q4_K_M, Q8_0, BF16, F32

    if quant is None:
        # CPU-only on a 4 GB-RAM box, etc. — fall back gracefully
        return "omnivoice"  # the existing in-process default

    # Probe load (sub-second timeout) — if the runtime binary or quant won't load,
    # don't make it default; keep falling back to the in-process VoiceStudioBackend
    try:
        VoiceStudioGGUFBackend.probe_load(quant=quant, timeout=5.0)
        return "omnivoice-gguf"
    except (RuntimeError, FileNotFoundError, subprocess.TimeoutExpired) as e:
        logger.warning("GGUF probe failed; falling back to in-process VoiceStudio: %s", e)
        return "omnivoice"

模式二:子进程 CLI 封装(omnivoice-tts)

说明: 每次 generate() 调用都会启动随附的 omnivoice-tts 二进制文件,传入模型、编解码器和参考音频参数,然后将输出的 WAV 文件读回 torch.Tensor。它位于 Phase 2 中 SubprocessBackend 的隔离 venv 内。 适用场景: 每次 GGUF 生成。C++ 二进制进程本身就是隔离边界;这里的每引擎“venv”基本上是空的(仅包含用于下载量化文件的 huggingface_hub),因为二进制文件的依赖已链接进自身。

# Source: omnivoice.cpp README CLI invocation [VERIFIED via WebFetch 2026-05-18]
# Combined with SubprocessBackend contract from Phase 2 ROADMAP

class VoiceStudioGGUFBackend(TTSBackend):
    id = "omnivoice-gguf"
    display_name = "VoiceStudio (GGUF)"

    @property
    def sample_rate(self) -> int:
        return 24_000  # Higgs Audio v2, same as base VoiceStudio

    def generate(self, text, *, ref_audio=None, ref_text=None, language="en", **kw):
        out_path = self._tmp_wav_path()
        args = [
            self._binary_path,                           # bin/omnivoice-tts-<plat>
            "--model",  str(self._base_quant_path),      # omnivoice-base-Q8_0.gguf
            "--codec",  str(self._tokenizer_quant_path), # omnivoice-tokenizer-Q8_0.gguf
            "--lang",   _iso_to_omnivoice_lang(language),
            "-o",       str(out_path),
        ]
        if ref_audio:
            args += ["--ref-wav", ref_audio]
            if ref_text:
                args += ["--ref-text", ref_text]

        # SubprocessBackend handles env scrubbing (HF_TOKEN injection from token_resolver,
        # log redaction per AUTH-05, mp.get_context("spawn") IPC supervision)
        self._subprocess_backend.run(args, stdin=text, timeout=120)

        wav, sr = soundfile.read(out_path)
        return torch.from_numpy(wav).unsqueeze(0)  # (1, n_samples)

需要避免的反模式

  • 通过原版 llama.cpp Python 绑定(llama-cpp-python)加载 GGUF。 这些量化文件使用自定义的 omnivoice-lm 架构;只有 omnivoice.cpp 能解析它们。尝试使用 llama-cpp-python 会静默加载随机 token,或以晦涩难懂的报错失败。
  • 在安装时构建 omnivoice.cpp。 这会给每台用户机器增加 C++17 工具链依赖,并在俄罗斯/中国镜像失败时出错(这是 Phase 3 的范畴,我们不能再重新讨论)。应在安装器中按平台捆绑预构建二进制文件;构建由 CI 完成。
  • 将 omnivoice-singing 当成完全独立的引擎类。 它使用相同的库、相同的架构、相同的编解码器、相同的 API;只有模型 ID 和 [singing] 文本标签不同。如果新建一个 SingingBackend(TTSBackend),其中 95% 的代码都是从 VoiceStudioBackend 复制粘贴而来,只改一个常量。应当改用子类。
  • 未经用户同意自动路由到唱歌模式。 SING-03 启发式检测作为默认路由建议是正确的;但在任何片段被提交到唱歌引擎渲染之前,配音 UI 中必须提供逐片段覆盖选项。最终路由由用户决定。
  • 在 macOS 上捆绑未进行代码签名的 omnivoice-tts。 macOS Sequoia 与 Gatekeeper 会像隔离未签名的 .app 一样隔离未签名的二进制文件(REL-05 已在跟踪证书相关工作;在该功能发布之前,Phase 1 中的 xattr -cr 解决方法仍然适用——需要在同一个错误 UI 路径中向用户展示)。
  • 每次启动都重新下载量化文件。 量化文件保存在 $HF_HUB_CACHE 中。下载检查是“SHA 固定的文件是否存在”——绝不是“为了安全总是重新下载”。

不要重复造轮子

问题 不要自行构建 改用 原因
GGUF 推理 一个自定义的 llama.cpp 分支,添加 omnivoice-lm 架构 omnivoice.cpp 二进制(上游、MIT、由量化作者编写) 量化作者就是运行时作者。我们自己 fork llama.cpp 会增加永久维护负担,且相比上游没有额外能力。
唱歌声音克隆模型架构 从头训练的唱歌模型 ModelsLab/omnivoice-singing(相同架构,已微调) 微调模型已存在并复用当前运行时;商业权利仍未解决。
用于唱歌模式路由的人声干声分离 自定义源分离网络 Demucs(已在配音管线中) 已经是依赖项,已集成,结果足以用于路由决策。
语音与唱歌分类器 为 SING-03 v0.3 训练的分类模型 基于 Demucs 人声干声的基音稳定性 + 能量启发式;UI 中可按片段用户覆盖 REQUIREMENTS.md 已把基于模型的分类器推迟到 v2。配合覆盖,启发式足够。
硬件探测 / VRAM 分档 新的 GPU 检测库 扩展现有的 backend/services/gpu_sandbox.py 探测 已检测 CUDA/MPS/ROCm/CPU;只需在此基础上增加 VRAM 分档。
GGUF 量化下载管理器 自定义文件镜像 + 断点续传 使用 $HF_HUB_CACHE 的 huggingface_hub 已固定版本,已处理令牌/镜像/续传/SHA 验证。

关键洞察: 阶段 4 几乎完全是把现有工具串联起来——每个架构原语都已存在(阶段 2 的 SubprocessBackend、huggingface_hub、Demucs、VoiceStudioBackend、硬件探测)。唯一真正的新产物是按平台捆绑的 omnivoice.cpp 二进制,它源码可用 + MIT 许可 + 6 个未关闭 issue / 59 次提交 / 规模足够小,可在固定提交上审计。


运行时状态清单

阶段 4 是增量式的(新引擎、新的捆绑二进制、HF 缓存中的新模型文件)。没有重命名任何内容。以下清单记录了阶段 4 引入的新运行时状态,以便未来的维护阶段能找到它们。

类别 阶段 4 引入的新运行时状态 所需操作
存储数据 $HF_HUB_CACHE/Serveurperso--OmniVoice-GGUF/、$HF_HUB_CACHE/ModelsLab--omnivoice-singing/、SQLite 设置列:gguf_quant_override、engine_default(已支持添加) 新增缓存条目(由 HF 缓存管理自动清理);通过项目的 init_db() 进行 SQLite 迁移
实时服务配置 无——不涉及外部服务 无
操作系统注册状态 无——没有 Task Scheduler、launchd、systemd 条目 无
密钥 / 环境变量 复用阶段 1 令牌解析器中的 HF_TOKEN;没有新密钥 无
构建产物 / 已安装包 按平台捆绑的 bin/omnivoice-tts-* 二进制;在 CI 中从固定的 omnivoice.cpp 提交 SHA 构建 阶段 0 CI 矩阵已支持跨平台——扩展它以构建四个二进制并按照 GATE-05 校验校验和

常见陷阱

陷阱 1:omnivoice.cpp 没有已发布的 macOS Metal 构建脚本

会出现什么问题: README 列出了 buildcpu.sh、buildcuda.sh、buildvulkan.sh、buildall.sh——但没有 buildmetal.sh 或 buildmacos.sh。README 在功能描述中提到 Metal,却没有对应的构建命令 [已通过 WebFetch 于 2026-05-18 验证]。

为什么会这样: 项目目前有 38 个 star / 59 次提交 / 6 个未关闭 issue;这很可能是维护缺口,而不是根本性障碍。GGML 本身支持 Metal,因此构建应该只差一个 cmake -DGGML_METAL=ON 标志。

如何避免: 阶段 4 Wave 1 包含一个“为 Apple Silicon Metal 构建 omnivoice-tts”的任务,并有明确的验收标准。如果该任务失败或遇到上游阻塞,将 SPIKE-01 从“默认克隆引擎”降级为“CUDA + Linux/Windows AVX2 上的可选替代方案”,并保留 VoiceStudioBackend(进程内 Python)作为 macOS 默认。决策文档必须记录这一点。

警告信号: macOS CI 无法生成 omnivoice-tts-darwin-arm64 二进制,或者该二进制在 M 系列 Mac 上产生全零或全噪声音频。

陷阱 2:Q4_K_M 的量化文件大小确实适合 VRAM 预算——但推理质量会下降

会出现什么问题: “Q4_K_M 适合 4 GB VRAM,就用它”的推理会导致在低 VRAM 硬件上将 Q4_K_M 设为默认;输出质量明显比 Q8_0 差,用户会认为 GGUF 引擎是倒退。

为什么会这样: 量化映射通常优化的是容量适配,而不是在适配容量前提下的质量。OmniVoice-GGUF 没有发布跨量化等级的质量基准(已验证——模型卡没有提供 MOS 或感知得分)。

如何避免: 对于任何具有 ≥ ~1 GB 可用 VRAM 的硬件(轻松覆盖 4 GB+ GPU),默认使用 Q8_0。仅在真正受限的硬件(≤ 2-3 GB VRAM、仅 CPU)上使用 Q4_K_M。让 quant_map.json 可被用户编辑,以便高保真用户强制使用 BF16/F32。在设置 UI 中展示该权衡:“自动选择:Q8_0(推荐)。覆盖:...”。

警告信号: 在 8 GB VRAM macOS/Windows 类别(按 GGUF-06)上的冒烟测试产生的输出明显差于进程内 VoiceStudioBackend。

陷阱 3:omnivoice-tts 二进制未签名,在 macOS Sequoia 上被隔离

会出现什么问题: 用户下载 VoiceStudio 安装程序,应用可以启动,但每次 GGUF 生成都失败,提示“cannot verify developer”。

为什么会这样: 与 #54(.app 隔离)相同的 Gatekeeper 路径;内部捆绑的二进制同样未签名,同样被隔离。

如何避免: 将 bin/omnivoice-tts-darwin-* 二进制视为与 #54 相同的 xattr -cr 规避面的一部分。扩展 docs/install/macos.md 中的 macOS 隔离检测探测,以及阶段 1 的错误 UI,使其在首次启动时也清除二进制的 xattr(或指导用户手动清除)。在 REL-05 的跟踪 issue 计划中跟踪真正的签名。

警告信号: macOS 用户报告“GGUF 在 Linux 上正常,在 Mac 上无错误消息地失败”——那就是 Gatekeeper 在静默终止进程。

陷阱 4:缺少 [singing] 标签时 omnivoice-singing 模型输出乱码

问题现象: 在缺少 [singing] 控制标签的情况下调用 omnivoice-singing 会产生乱码或近乎静音的输出;用户会反馈“歌唱引擎坏了”。 原因分析: 微调模型是在带标签的数据上训练的;标签就是门禁。 规避方法: VoiceStudioSingingBackend.generate() 会自动注入 [singing],除非提示词已经以 [ 前缀标签开头(这样高级用户可以手动组合 [singing] [happy] 等)。在引擎卡 README 和设置中展示支持的标签集合。 预警信号: 首次端到端歌唱模式配音测试产生的输出像说话而非旋律。

陷阱 5:SING-03 启发式规则将歌剧式/持续元音说话误分类为 “speech”(或将带颤音的说话误分类为 “singing”)

问题现象: 音高稳定性启发式规则会将一个持续保持元音的歌手的片段标记为 “speech”,因为音高过于稳定;或者将颤音很重的演员标记为 “singing”,因为音高波动大。 原因分析: 启发式规则是单维度的。真实的说话与歌唱区分需要多特征(节奏、和声结构、颤音模式)。 规避方法: 将启发式输出视为路由建议,而不是最终决定。在配音 UI 中展示预测的分段,并提供逐段覆盖(REQUIREMENTS SING-03 已要求“每个分段提供高级用户覆盖”)。SING-05 中的验收标准是“在说话和歌唱分段之间保持一致的声音身份”——这可以独立于分类器精度进行测试。 预警信号: 冒烟测试(SING-05)生成的最终混音中分段路由正确,但用户每 30 秒需要手动修正 5 个以上的分段。

陷阱 6:Phase 4 的 PR 意外回退 Phase 3 的镜像可靠性工作

问题现象: 在安装程序中捆绑 omnivoice-tts 二进制文件会改变安装程序清单;如果处理不慎,Phase 3 的镜像级联测试假设二进制大小为 X,回归测试套件就会失败。 原因分析: ROADMAP 明确说明 Phase 4 可以与 Phase 3 并行进行,但不得回退镜像可靠性。自然的失败模式是“安装程序体积变大,慢速镜像配置上的网络冒烟测试超时”。 规避方法: GATE-03(release.yml 安装程序冒烟测试)必须在任何 Phase 4 PR 合并之前使用捆绑的二进制文件运行。四个二进制文件的捆绑包压缩后增加约 12-16 MB——完全在现有安装程序体积范围内,但需要对照 Phase 3 的镜像时序基线进行验证。 预警信号: 当 Phase 4 的 PR 落地时,Phase 3 的镜像级联测试开始在仅清华/阿里云网络配置上超时。


代码示例

硬件探测扩展(GGUF-01)

# Source: extends backend/services/gpu_sandbox.py (already detects CUDA/MPS/ROCm/CPU)
# Confidence: HIGH for shape (existing pattern); MEDIUM for the exact VRAM thresholds
#             (no published OmniVoice-GGUF quality-per-VRAM benchmark)

from dataclasses import dataclass
from typing import Literal

ComputeClass = Literal["cpu", "low-vram", "mid-vram", "high-vram"]

@dataclass(frozen=True)
class HardwareCapabilities:
    backend: Literal["cuda", "mps", "rocm", "cpu"]
    vram_mb: int          # 0 for CPU/MPS-unified-memory bookkeeping; MPS reports via mps.current_allocated_memory()
    compute_class: ComputeClass

def detect_capabilities() -> HardwareCapabilities:
    if torch.cuda.is_available():
        free, total = torch.cuda.mem_get_info()  # bytes
        vram_mb = total // (1024 * 1024)
        return HardwareCapabilities(
            backend="cuda",
            vram_mb=vram_mb,
            compute_class=_bucket(vram_mb),
        )
    if torch.backends.mps.is_available():
        # MPS unified memory: treat half of system RAM as effective VRAM ceiling
        ram_mb = psutil.virtual_memory().total // (1024 * 1024)
        vram_mb = ram_mb // 2
        return HardwareCapabilities(
            backend="mps",
            vram_mb=vram_mb,
            compute_class=_bucket(vram_mb),
        )
    # CPU
    return HardwareCapabilities(backend="cpu", vram_mb=0, compute_class="cpu")

def _bucket(vram_mb: int) -> ComputeClass:
    if vram_mb >= 12_000:  return "high-vram"   # → BF16
    if vram_mb >=  4_000:  return "mid-vram"    # → Q8_0  (recommended default)
    if vram_mb >=  1_000:  return "low-vram"    # → Q4_K_M
    return "cpu"                                # → Q4_K_M (CPU inference)

quant_map.json(GGUF-02)

// Source: synthesized from OmniVoice-GGUF model card quant table + recommended-defaults heuristic
// Shippable JSON so the table can update without an app release
{
  "_meta": {
    "schema_version": 1,
    "source_model": "Serveurperso/OmniVoice-GGUF",
    "source_commit_sha": "<PIN-AT-PHASE-4-PLAN-TIME>",
    "runtime": "omnivoice.cpp",
    "runtime_commit_sha": "<PIN-AT-PHASE-4-PLAN-TIME>"
  },
  "high-vram": {
    "base": "omnivoice-base-BF16.gguf",
    "tokenizer": "omnivoice-tokenizer-BF16.gguf",
    "rationale": "≥12 GB VRAM — quality-first; ~1.6 GB total VRAM use"
  },
  "mid-vram": {
    "base": "omnivoice-base-Q8_0.gguf",
    "tokenizer": "omnivoice-tokenizer-Q8_0.gguf",
    "rationale": "4-12 GB VRAM — recommended balance; ~945 MB VRAM use"
  },
  "low-vram": {
    "base": "omnivoice-base-Q4_K_M.gguf",
    "tokenizer": "omnivoice-tokenizer-Q4_K_M.gguf",
    "rationale": "1-4 GB VRAM — minimal footprint; ~659 MB VRAM use"
  },
  "cpu": {
    "base": "omnivoice-base-Q4_K_M.gguf",
    "tokenizer": "omnivoice-tokenizer-Q4_K_M.gguf",
    "rationale": "CPU-only — Q4_K_M to keep latency tolerable"
  }
}

分段检测器骨架(SING-03 启发式规则)

# Source: synthesized from REQUIREMENTS.md SING-03 ("pitch-stability + energy heuristic")
# Confidence: MEDIUM — heuristic is feasible from existing toolkit (librosa-style pitch + RMS),
#             but no published benchmark for this exact heuristic on VoiceStudio's dubbing corpus.
#             User override per segment is the safety net.

from dataclasses import dataclass
from typing import Literal

SegmentKind = Literal["speech", "sing"]

@dataclass(frozen=True)
class Segment:
    start_s: float
    end_s: float
    kind: SegmentKind
    confidence: float  # 0..1 — surface in UI for user override

def detect_singing_segments(
    vocal_stem_wav: "np.ndarray",
    sr: int,
    *,
    min_segment_s: float = 1.0,
) -> list[Segment]:
    """
    Per-frame analysis on the Demucs vocal stem:
      • pitch via librosa.yin (or torch equivalent already in deps)
      • energy via RMS
    Sing-flag if pitch is sustained > N frames AND energy is above threshold.
    Merge adjacent flagged frames into segments ≥ min_segment_s.
    Return segments with a coverage gap = inferred speech segment.
    """
    ...  # implementation in segment_detector.py

引擎注册

# Source: extends backend/services/tts_backend.py _REGISTRY (existing)
# Confidence: HIGH (existing pattern preserved)

# In tts_backend.py (additive):
class VoiceStudioGGUFBackend(TTSBackend):
    id = "omnivoice-gguf"
    display_name = "VoiceStudio (GGUF, hardware-adaptive)"
    # … see Pattern 2 above

class VoiceStudioSingingBackend(VoiceStudioBackend):
    id = "omnivoice-singing"
    display_name = "VoiceStudio (singing)"
    # See Architecture Diagram inset

_REGISTRY.update({
    "omnivoice-gguf":    VoiceStudioGGUFBackend,
    "omnivoice-singing": VoiceStudioSingingBackend,
})

技术现状

旧方法 当前方法(2026 年) 变更时间 影响
每个应用使用单一的进程内模型 每个引擎使用独立子进程,并共享 HF 缓存 本里程碑的阶段 2(促使了此变更) 阶段 4 可以干净地继承这一基线;两个新引擎都符合该模式
GGUF + llama.cpp 通用运行时 面向模型架构的专用 GGUF 运行时(omnivoice.cpp、whisper.cpp 等) 2025-2026 —— 随着 TTS/音频模型采用带有自定义架构的 GGUF 格式 无法将 llama.cpp 生态复用于 VoiceStudio 量化模型;需要专用运行时。只要量化格式保持当前状态,我们就只能使用 omnivoice.cpp。
将 transformers 的 text-to-speech pipeline 作为通用接口 当模型超出 pipeline 契约时,改用模型专属 Python 包(omnivoice、supertonic 等) 持续进行中 omnivoice-singing 模型卡提到了一条 pipeline("text-to-speech", model="ModelsLab/omnivoice-singing") 路径,但我们目前随产品发布的是现有 VoiceStudio 库——继续使用它。

已弃用/过时: - 仅凭频谱包络手工区分演唱与语音(对持续元音语音有很高的误报率)。应将音高稳定性与能量结合,进行门控并覆盖。 - 每个引擎各自的自定义凭据存储。阶段 1 的 token 解析器取代了这种做法;两个新引擎都从解析器继承 HF_TOKEN。


假设记录

# 假设 相关章节 如果出错的风险
A1 omnivoice.cpp Metal 构建路径可以通过 cmake -DGGML_METAL=ON 或类似的 GGML 标志启用 陷阱 #1 如果为假:macOS Apple Silicon 需要走 Vulkan-via-MoltenVK 路径或进程内回退,将 SPIKE-01 降级为“仅在非 Apple Silicon 上可选启用”
A2 按平台分发的 omnivoice-tts 二进制文件压缩后大小约为 3-4 MB,总计约 12-16 MB 模式 2 / 陷阱 #6 如果大得多:安装包体积增长到足以给阶段 3 的镜像时间预算带来压力;可能需要改为在安装时获取二进制文件。
A3 音高稳定性 + 能量启发式足以达到 SING-03 质量标准(支持按片段覆盖) 陷阱 #5 如果为假:SING-05 冒烟测试会经常失败 → 需要更明确地将 SING-03 基于模型的路径推迟到 v2,使 v0.3 只能手动标记片段。
A4 quant_map.json 的阈值(1 GB / 4 GB / 12 GB)与不同硬件上的感知质量断崖相匹配 模式 1,代码示例 如果阈值有误:处于临界硬件上的用户会看到质量下降。缓解措施:以可更新的 JSON 形式发布,并允许在设置中覆盖。
A5 huggingface_hub 的 SHA 固定下载能在不同 huggingface_hub 次版本间生成确定的文件路径 代码示例 如果为假:HF-CLI 行为变化会破坏固定版本查找。缓解措施:在 pyproject.toml 中固定 huggingface_hub 的次版本号。
A6 阶段 2 的 SubprocessBackend 支持传递 CLI 参数 + 标准输入 + 读取输出文件(而不只是 stdin/stdout 流式传输) 模式 2 如果阶段 2 只设计 stdin/stdout IPC:GGUF 后端就必须先把文本写入临时文件,或者通过管道捕获并做小幅重构。
A7 macOS Gatekeeper 的 xattr 清除能自然延伸到捆绑的子二进制文件 陷阱 #3 如果为假:即使执行了 xattr -cr .app,内部的 omnivoice-tts 二进制仍处于隔离状态;用户需要再执行一条命令。可记录为临时解决方案。
A8 ModelsLab/omnivoice-singing 模型可以通过与上游 k2-fsa/OmniVoice 模型相同的 omnivoice PyPI 库加载 技术栈 — SPIKE-02 如果为假:就需要通过 transformers pipeline 调用(模型卡提到这可行),从而引入第三种加载路径。风险较低——根据模型卡,两个模型共享 Qwen3-0.6B + Higgs Audio v2 架构。

未决问题

  1. GGUF 引擎是否应成为所有硬件的默认引擎,还是仅用于 CPU + 低 VRAM、且明确优于 Python 进程内路径的场景?
  2. 我们已经知道:Q8_0(约 945 MB VRAM)只占进程内 Python 模型体积的一小部分;在相同硬件上延迟可能更快,但目前没有发布的延迟基准数据。
  3. 尚不清楚:在 16 GB VRAM CUDA 设备上,进程内 Python 路径是否会在延迟或质量上胜过 Q8_0。朴素的预期是“GGUF 在 CPU/低 VRAM 上胜出,在中等 VRAM 上打平,进程内 Python 在高 VRAM 上胜出。”
  4. 建议:阶段 4 的第一波包含一项头对头基准测试任务,覆盖 GGUF-06 中列出的三类硬件。决策文档会记录由此产生的默认策略。GGUF-05 已经规定“在通过探测的硬件上默认启用,并允许覆盖回退”——未决问题只是“通过”的边界落在哪里。

  5. omnivoice-singing 在跨语言演唱上的质量(按其模型卡所述,“外推质量参差不齐”)是否达到 SING-05 验收标准?

  6. 我们已经知道:母语演唱是训练过的场景;跨语言演唱被承认是外推。
  7. 尚不清楚:SING-05 冒烟测试(“为一段 30 秒的语音 + 演唱混合源配音”)会使用同语言还是跨语言演唱。
  8. 建议:SING-05 应明确在测试混合中同时运行同语言和跨语言演唱。此处的 NO-GO 结论是部分的:同语言演唱保持 GO,跨语言演唱标记为 v2。

  9. 如果 GGUF 探测成功,但用户的网络无法访问 HF 以下载量化模型(阶段 3 中提到的中国/俄罗斯路径),正确的行为是什么?

  10. 我们已经知道:阶段 3 为 Python 安装建立了镜像级联(UV_PYTHON_INSTALL_MIRROR),而非模型下载。HF 自身有 HF_ENDPOINT 环境变量,可路由到 HF 镜像。
  11. 尚不清楚:阶段 4 是否继承阶段 3 针对模型下载的镜像选择,还是 HF 镜像属于独立的阶段 4 任务。
  12. 建议:在阶段 4 PLAN 中记录为“若设置了 HF_ENDPOINT 则使用之,否则使用默认 HF——除阶段 1 token 解析器和阶段 3 安装镜像已提供的机制外,不引入新的镜像级联。”如果量化模型下载失败,应回退到进程内 VoiceStudioBackend,而不是阻塞引擎。

  13. 现有的配音流水线是否已经具备按片段路由的 API,还是 SING-02 需要新的流水线基础设施?

  14. 已知情况:dub_pipeline.py 存在于 backend/services/ 中;其片段路由接口的具体细节需要阅读该文件。
  15. 不确定之处:歌唱模式路由是 50 行的小改动,还是 500 行的大重构。
  16. 建议:Phase 4 Wave 2 的第一项任务是花 1 小时通读 dub_pipeline.py,以准确评估 SING-02 的范围。如果重构较大,则将范围缩减为“歌唱模式应用于整个配音任务而非按片段”,纳入 v0.3,并将按片段支持推迟到 v0.4。

环境可用性

依赖项 需要方 可用 版本 回退方案
omnivoice Python 库 SPIKE-02 后端(VoiceStudioSingingBackend) ✓(已通过基础引擎锁定版本) 0.1.5 —
huggingface_hub 两个 spike(量化 + 歌唱模型下载) ✓(Phase 1 依赖) ≥1.12.x —
omnivoice-tts 二进制(由 omnivoice.cpp 构建) SPIKE-01 推理 ✗ 调研时不可用 — 随安装器按平台打包;如果 Apple Silicon Metal 构建受阻,则在 macOS 上回退到进程内 VoiceStudioBackend
C++17 工具链 + CMake(仅用于 CI 二进制构建) CI 二进制构建任务 ✓(标准 GitHub 托管运行器) 因环境而异 —
SubprocessBackend 原语 两个 spike 的引擎隔离 ✗ 调研时不可用(Phase 2 依赖) — 硬依赖 —— Phase 2 发布前,Phase 4 PR 无法合并
Demucs SING-02/SING-03 人声分轨路由 ✓(现有配音流水线) 已锁定版本 —
音高提取库(librosa.yin 或 torch 等价物) SING-03 启发式逻辑 ✓(已通过 Demucs/配音在依赖中) 已锁定版本 —
macOS 代码签名证书 macOS Sequoia 上的 GGUF 二进制 ✗(REL-05 追踪问题,不在本里程碑范围内) — 与 #54 相同的 xattr -cr 变通方案;记录在 docs/install/macos.md 中

无回退方案的缺失依赖: 无(二进制构建通过 CI 在我们的控制范围内)。 有回退方案的缺失依赖: Apple Silicon Metal 上的 omnivoice-tts —— 如果 Metal 构建未能实现,则优雅降级为进程内 VoiceStudioBackend。


验证架构

测试框架

属性 值
框架 pytest(现有)+ 冒烟脚本(scripts/smoke-test.sh 模式)
配置文件 pyproject.toml(PR #50 后限定范围的 pytest)
快速运行命令 uv run pytest tests/engines/test_omnivoice_gguf.py tests/engines/test_omnivoice_singing.py -x
完整套件运行命令 uv run pytest tests/engines/ tests/services/test_dub_pipeline_singing.py -x

阶段需求 → 测试映射

需求 ID 行为 测试类型 自动化命令 文件是否存在?
SPIKE-01 存在包含 GO/NO-GO 及理由的决策文档;引用了模型卡事实 文档存在性检查 test -f .planning/decisions/SPIKE-01-gguf.md && grep -q 'Decision: GO\|Decision: NO-GO' .planning/decisions/SPIKE-01-gguf.md ❌ Wave 0(本次调研即输入)
SPIKE-02 存在包含 GO/NO-GO 及理由的决策文档 文档存在性检查 test -f .planning/decisions/SPIKE-02-singing.md && grep -q 'Decision: GO\|Decision: NO-GO' .planning/decisions/SPIKE-02-singing.md ❌ Wave 0
GGUF-01 硬件探测为当前平台返回有效的 HardwareCapabilities 单元测试 pytest tests/engines/test_hardware_probe.py::test_detect_capabilities_returns_valid_class -x ❌ Wave 0
GGUF-02 quant_map.json 是有效的 JSON,每个 compute_class 都映射到现有的量化文件名 单元测试 pytest tests/engines/test_omnivoice_gguf.py::test_quant_map_valid -x ❌ Wave 0
GGUF-03 后端能够启动 omnivoice-tts、传递参数、读取输出的 WAV 集成测试(需要二进制) pytest tests/engines/test_omnivoice_gguf.py::test_subprocess_generate_3s -x ❌ Wave 0
GGUF-04 设置 → 量化覆盖持久化到 SQLite,并在下次启动时重新加载 集成测试 pytest tests/services/test_settings_store.py::test_quant_override_round_trip -x ❌ Wave 0
GGUF-05 select_default_engine() 在通过探测的硬件上返回 omnivoice-gguf,在探测失败时回退 单元测试(模拟探测) pytest tests/engines/test_omnivoice_gguf.py::test_default_selection -x ❌ Wave 0
GGUF-06 在 3 类硬件上进行端到端克隆 3 秒 —— 量化与表格匹配,输出可理解 冒烟测试(手动 + CI 矩阵) scripts/smoke-gguf.sh --hardware-class {cpu,mid,high} ❌ Wave 0(CI 矩阵集成)
SING-01 VoiceStudioSingingBackend 加载,自动注入 [singing] 标签生成 1 秒音频 单元测试 pytest tests/engines/test_omnivoice_singing.py::test_generate_with_auto_tag -x ❌ Wave 0
SING-02 配音流水线在开关开启时将人声分轨路由到歌唱引擎;保留乐器部分 集成测试 pytest tests/services/test_dub_pipeline_singing.py::test_singing_mode_preserves_instrumental -x ❌ Wave 0
SING-03 分段检测器在已知混合片段上返回包含 kind + confidence 的有效 Segment 列表 单元测试 pytest tests/services/test_segment_detector.py::test_mixed_clip_routing -x ❌ Wave 0
SING-04 许可展示必须区分代码、权重和上游限制;接受/执行仍有待实现 单元测试 + UI pytest tests/engines/test_omnivoice_singing.py::test_license_gate -x ❌ Wave 0
SING-05 30 秒混合语音+歌唱片段进行端到端配音;两个片段均可理解,乐器部分保留 冒烟测试 scripts/smoke-singing.sh tests/fixtures/mixed-30s.wav ❌ Wave 0

采样频率

  • 每个任务提交: uv run pytest tests/engines/test_omnivoice_gguf.py tests/engines/test_omnivoice_singing.py -x
  • 每个 Wave 合并: 完整测试套件(uv run pytest tests/engines/ tests/services/test_dub_pipeline_singing.py -x)+ 跨平台冒烟测试(scripts/smoke-gguf.sh、scripts/smoke-singing.sh)
  • 阶段门禁: 在执行 /gsd:verify-work 之前,完整测试套件全绿 + Phase 0 跨平台 CI 矩阵全绿。

Wave 0 覆盖缺口

  • [ ] tests/engines/test_hardware_probe.py —— 覆盖 GGUF-01
  • [ ] tests/engines/test_omnivoice_gguf.py —— 覆盖 GGUF-02、GGUF-03、GGUF-05
  • [ ] tests/engines/test_omnivoice_singing.py —— 覆盖 SING-01、SING-04
  • [ ] tests/services/test_dub_pipeline_singing.py —— 覆盖 SING-02
  • [ ] tests/services/test_segment_detector.py —— 覆盖 SING-03
  • [ ] tests/services/test_settings_store.py::test_quant_override_round_trip —— 扩展现有测试文件以覆盖 GGUF-04
  • [ ] scripts/smoke-gguf.sh —— 覆盖 GGUF-06(按硬件类别参数化,用于 CI 矩阵)
  • [ ] scripts/smoke-singing.sh —— 覆盖 SING-05
  • [ ] tests/fixtures/mixed-30s.wav —— 30 秒混合语音+歌声测试资源(必须提交入库)

安全领域

适用的 ASVS 类别

ASVS 类别 是否适用 标准控制项
V2 身份验证 是 复用 Phase 1 的 token_resolver(HF token,无新增认证面)
V3 会话管理 否 无会话——本地优先的桌面应用
V4 访问控制 部分 默认引擎 + quant 的设置覆盖——与现有设置面板的访问控制面相同(仅限已登录的 OS 用户)
V5 输入验证 是 在传入子进程前,根据 quant_map.json 允许列表验证 quant 文件名;验证 ref-audio 路径保持在应用允许的目录内
V6 密码学 部分 对随附的 omnivoice-tts 二进制文件进行 SHA-256 校验(依据 GATE-05);HF 模型文件使用 HF 自身的 SHA 验证

针对 {Phase 4 技术栈} 的已知威胁模式 {#known-threat-patterns-for-phase-4-stack}

模式 STRIDE 标准缓解措施
随附二进制文件在安装后被篡改 篡改 依据 GATE-05 在发布说明中公布 SHA-256;在首次启动及每次加载 quant 时验证
通过 UI 注入任意子进程参数(例如用户输入控制的 --ref-wav) 篡改 / 权限提升 子进程参数由基于应用目录的强类型 Path 对象构成;拒绝 omnivoice_data/ 或 $HF_HUB_CACHE 之外的路径;绝不使用 shell=True
quant 下载期间通过网络 MITM(中间人)提供恶意 GGUF 篡改 huggingface_hub 在下载时强制执行 SHA 验证;在 quant_map.json 中通过 commit SHA 固定 quant
HF token 泄露到 omnivoice-tts 子进程 stderr 信息泄露 AUTH-05 日志过滤器适用于子进程 stderr 捕获;子进程通过 Phase 2 契约继承环境(参数中不含 token)
使用 quant 覆盖 UI 加载攻击者控制的 GGUF 路径 篡改 / 权限提升 quant 覆盖仅为基于 quant_map.json 所列文件的下拉框;无自由格式路径输入(类比 INST-09 镜像允许列表)
配音流水线以受攻击者影响的文件名写入生成的音频 篡改 输出路径由作业 ID + 临时目录构成;用户提供的名称需经过 os.path.basename + 字符允许列表处理

Spike 方法论

本项研究如何执行 SPIKE 协议

  1. Web 抓取模型卡片 [已完成 2026-05-18]:确认 Serveurperso/OmniVoice-GGUF 和 ModelsLab/omnivoice-singing 均存在、均为公开、均为 k2-fsa/OmniVoice 的后代,并共享上游血统。原有的许可证兼容性结论已撤回;参见上文更正及 huggingface.co/k2-fsa/OmniVoice。
  2. Web 抓取运行时仓库 [已完成 2026-05-18]:确认 github.com/ServeurpersoCom/omnivoice.cpp 是这些 quant 的唯一运行时;记录了 MIT 许可证、构建脚本集、CLI 调用模式以及维护状态(38 stars、59 commits、6 个未解决问题)。
  3. PyPI 包验证 [已完成 2026-05-18]:确认 PyPI 上的 omnivoice 0.1.5(2026-04-28,Apache-2.0)——已是项目依赖,SPIKE-02 无需新增依赖。
  4. 架构兼容性检查 [已完成]:两个下游模型与项目现有默认 VoiceStudioBackend(Qwen3-0.6B + Higgs Audio v2 编解码器,24 kHz 单声道)共享相同架构。这是支撑性的关键发现,它将两个 spike 从“新引擎”重新定义为“已在交付中的引擎变体”。
  5. 集成形态提案 [已完成]:记录了两个后端类的形态、子进程接线、硬件探测扩展、片段检测器骨架——全部使用现有项目原语。
  6. GO/NO-GO 建议 [已完成]:见下文,附明确理由。
  7. 决策文档模板 [见下文 ## Decision Doc Templates]:人工评审后即可复制到 .planning/decisions/SPIKE-01-gguf.md 和 SPIKE-02-singing.md。

本项研究刻意未做的事项

  • 在研究员机器上运行二进制文件。 Spike 代码执行属于 Phase 4 Wave 1(依据 GGUF-06 的 scripts/smoke-gguf.sh)。研究止步于“证明集成形态可行且产物真实存在”。
  • 跨 quant 进行质量基准测试。 目前尚无公开基准;头对头 MOS 测试属于 Wave 1 的基准测试任务,并为最终 quant_map.json 的默认值提供依据。
  • 为 omnivoice.cpp 或各 quant 最终固定 commit SHA。 固定操作应在集成时写入 PLAN.md,而非研究阶段——若研究先于 PR 进行,固定值会漂移。

GO / NO-GO 建议

SPIKE-01(Serveurperso/OmniVoice-GGUF):技术性 GO

这仅确立技术可行性。免费应用验收还需要通过引擎验收中的权利门槛,包括所有者批准,以及受限引擎的首次使用披露。

理由:

  • 历史模型/运行时/血统审查日期为 2026-05-18;商业许可授权结论已被上文更正取代。
  • ✓ 与 VoiceStudio 现有默认模型相同的底层模型——这是对现有交付内容的量化,而非新的引擎架构。最坏情况下,它只是在特定硬件类别上无法超越进程内 Python 路径,而我们会保留该路径作为后备方案。
  • 商业模型许可授权已撤回;运行时与权重条款需单独记录。
  • ✓ 依据 omnivoice.cpp 构建矩阵,通过 CUDA / Vulkan / Metal / CPU 实现跨平台 [附 假设 A1 注意事项——Metal 构建脚本未包含在已发布 README 中,必须在 Wave 1 验证]。
  • ✓ 硬件适配天然契合用户所述的价值主张——“首次运行即可真正工作”适用于广泛的硬件范围:Q4_K_M 约 659 MB 显存即可让引擎在 4 GB GPU 上运行,而这类 GPU 目前在进程内 Python 路径上会退化为 CPU 执行。

GO 条件(必须在 PLAN.md 中成立): 1. Phase 2 的 SubprocessBackend 原语先行落地。 2. Wave 1 包含一项针对 macOS Apple Silicon Metal 二进制的构建与验证任务;若该任务失败,则将范围收缩至非 Apple Silicon 平台,并在 macOS 上使用进程内 VoiceStudioBackend 作为已记录的失效备选。 3. omnivoice.cpp 在集成时按 commit SHA 固定;量化模型在 quant_map.json 中按 commit SHA 固定。 4. 捆绑的二进制文件采用与 #54 相同的 xattr -cr 处理方法。

SPIKE-02(ModelsLab/omnivoice-singing):GO,范围缩减 ✓

理由: - 历史模型/运行时审查日期为 2026-05-18;商业许可批准已被上述内容取代。 - ✓ 相同的 omnivoice PyPI 库、相同的架构、相同的编解码器 — 关键发现:这不是一个新引擎,而是一个由我们已有的引擎类消费的新模型 ID。VoiceStudioSingingBackend 是一个 ≤30 行的子类。 - 先前关于训练用途与使用用途的许可结论被撤回。歌唱模型在获得商业批准之前需要上游澄清。 - ✓ 硬件:与现有 VoiceStudio 的占用相同;可在与现有引擎兼容的硬件上运行。

此研究所覆盖的范围缩减: - SING-01:按原样保留 — 新的后端类很小,但真实存在。 - SING-02:取决于 dub_pipeline.py 的片段路由接口 — 研究建议将代码阅读任务作为 Wave 2 的第一个任务,以便准确界定范围;如果现有流水线不支持按片段路由,则将范围缩减为“歌唱模式应用于整个配音任务”(适用于 v0.3)。 - SING-03:依据现有的 v2 延后决定,仅使用启发式方法 — RESEARCH 提出了具体的启发式方法(在 Demucs 人声主干上评估音高稳定性 + 能量,并支持按片段覆盖)。 - SING-04 / SING-05:按原样保留。

GO 条件(必须在 PLAN.md 中成立): 1. Phase 2 的 SubprocessBackend 原语先行落地(与 Phase 4 的宿主模式保持一致,即使歌唱后端是通过现有 omnivoice 库在进程内运行 — 一致性对引擎注册表很重要)。 2. SING-02 的范围在 Wave 2 对 dub_pipeline.py 进行代码阅读后决定。 3. SING-05 的验收标准允许母语歌唱通道,并将跨语言歌唱标记为尽力而为(依据模型卡中“外推质量不一”的免责声明)。

Phase 4 综合结果

两个 spike 均为 GO → 13/13 项需求保持范围内。没有需求移至“范围之外”。决策文档确认这些发现,规划者据此制定计划,无需对 REQUIREMENTS.md 进行改动。

决策文档模板

两个文件,由规划者在 Phase 4 的 PLAN.md 锁定后编写,并以本研究作为主要输入。此处提供骨架模板,以便规划者随时可用。

.planning/decisions/SPIKE-01-gguf.md

# SPIKE-01: Adopt `Serveurperso/OmniVoice-GGUF` as hardware-adaptive default cloning engine

**Status:** Proposed (research-supported) — awaiting Phase 2 SubprocessBackend merge
**Date:** 2026-05-18
**Decision-makers:** [maintainer]
**Related:** ROADMAP Phase 4; REQUIREMENTS GGUF-01..06; this Phase 4 RESEARCH.md

## Context

VoiceStudio v0.2.7 ships `k2-fsa/OmniVoice` (separate code/weight terms; 0.6B Qwen3 backbone, Higgs Audio v2 codec) as its default voice-cloning engine via `backend/services/tts_backend.py:VoiceStudioBackend`. The Python in-process path requires PyTorch + CUDA / MPS / CPU.

`Serveurperso/OmniVoice-GGUF` publishes 4 quantizations of the same model (Q4_K_M / Q8_0 / BF16 / F32) consumable through the MIT-licensed `omnivoice.cpp` runtime (a custom GGML-based C++ inference binary). This decision is whether to integrate the GGUF engine as a hardware-adaptive default with overridable fallback.

## Decision

**Technical GO only** — integrate per GGUF-01..06; engine acceptance remains conditional on the rights gate in `docs/engine-acceptance.md`.

## Consequences

**Positive:**
- 4 GB-VRAM GPUs (currently falling back to CPU on the in-process path) get GPU-backed cloning via Q4_K_M.
- Smaller VRAM footprint = stays out of the way of other engines when users run multiple in one session.
- Model licensing needs separate review from the runtime licence; see the correction above.

**Negative / risk:**
- Adds a maintained-by-others C++ runtime to the dependency graph (`omnivoice.cpp`, 38 stars at decision time).
- Adds 12-16 MB of platform binaries to the installer.
- macOS code signing scope expands by 4 binaries (track via REL-05).

**Mitigations:**
- Pin `omnivoice.cpp` by commit SHA; rebuild from pinned SHA in CI.
- In-process `VoiceStudioBackend` remains and is the fallback if any GGUF step fails.
- macOS Apple Silicon Metal build is verified in Wave 1; if blocked, downgrade SPIKE-01 default on macOS to in-process path.

## Sources

- `.planning/phases/04-adaptive-specialty-engines-spike-first/04-RESEARCH.md` (this research)
- https://huggingface.co/Serveurperso/OmniVoice-GGUF (verified 2026-05-18)
- https://github.com/ServeurpersoCom/omnivoice.cpp (verified 2026-05-18)
- https://huggingface.co/k2-fsa/OmniVoice (upstream; separate code/weight terms)

.planning/decisions/SPIKE-02-singing.md

# SPIKE-02: Adopt `ModelsLab/omnivoice-singing` as singing variant of the existing engine

**Status:** Proposed (research-supported) — awaiting Phase 2 SubprocessBackend merge
**Date:** 2026-05-18
**Decision-makers:** [maintainer]
**Related:** ROADMAP Phase 4; REQUIREMENTS SING-01..05; this Phase 4 RESEARCH.md

## Context

`ModelsLab/omnivoice-singing` is a finetune of `k2-fsa/OmniVoice` (commercial rights unresolved; same Qwen3-0.6B backbone, same Higgs Audio v2 codec, same `omnivoice` PyPI library) trained on additional singing + emotion-tagged data. Activated by a `[singing]` text control tag at generation time.

VoiceStudio's dubbing pipeline currently routes vocal stems (via Demucs) through the default TTS engine, which produces speech output even on sung source material. This decision is whether to integrate the singing finetune as a routed alternative for sung segments.

## Decision

**GO with reduced scope** — integrate per SING-01..05, with SING-02 scope-decided after a Wave 2 read of `dub_pipeline.py`.

## Consequences

**Positive:**
- Sung segments of dubbed content produce sung output (currently produces unsuitable speech-like output).
- Zero new Python deps — same `omnivoice` library already shipping.
- ≤30-line backend subclass.

**Negative / risk:**
- Heuristic segmentation (SING-03 pitch-stability + energy) is approximate; user override per segment is the safety net.
- Cross-language singing quality is acknowledged by the model card as variable.

**Mitigations:**
- Per-segment override available in dubbing UI before commit.
- SING-05 acceptance scoped to native-language singing pass; cross-language flagged as best-effort.
- Model-based classifier deferred to v2 per REQUIREMENTS.md.

## Sources

- `.planning/phases/04-adaptive-specialty-engines-spike-first/04-RESEARCH.md` (this research)
- https://huggingface.co/ModelsLab/omnivoice-singing (verified 2026-05-18)
- https://huggingface.co/k2-fsa/OmniVoice (upstream)
- https://pypi.org/project/omnivoice/ (0.1.5, 2026-04-28)

Phase 2 依赖关系

Phase 4 受制于 Phase 2 的 SubprocessBackend 原语落地。具体而言,Phase 4 的 PLAN.md 依赖 Phase 2 的 RESEARCH.md 确认以下内容:

  • SubprocessBackend.run(args, stdin, timeout) 能够将 CLI 参数与 stdin 传递给子进程(用于按上文 Pattern 2 调用 GGUF 二进制文件)。
  • mp.get_context("spawn") IPC 已在 macOS Apple Silicon 上验证(见 SUMMARY.md 中 Phase 2 的开放问题)。
  • 每个引擎的 venv 引导程序会从父进程继承 HF_HOME(见 ENGINE-02),因此量化下载会落入现有的 $HF_HUB_CACHE。
  • is_available() 包装(ENGINE-05)意味着某个平台上损坏的 omnivoice-tts 二进制文件不会阻止应用在其他平台上启动。

在 Phase 2 RESEARCH 落地之前,Phase 4 可以完成以下事项:

  • 通过网络抓取模型卡(本 RESEARCH.md)。
  • 起草 .planning/decisions/SPIKE-01-gguf.md 和 SPIKE-02-singing.md。
  • 在 CI 中为四个目标平台构建 omnivoice-tts 二进制文件(该构建本身不依赖 Phase 2)。

在 Phase 2 RESEARCH 落地之前,Phase 4 无法完成以下事项:

  • PLAN.md 中引用 SubprocessBackend 内部实现的任务措辞(例如具体的 IPC API、日志脱敏过滤器位置、venv 引导辅助函数签名)。

这是规划层面的门禁,而不是研究层面的阻塞。研究到此完成;PLAN.md 需等待 Phase 2 研究。


来源

主要来源(高置信度 — 2026-05-18 在线验证)

  • https://huggingface.co/Serveurperso/OmniVoice-GGUF — 模型卡、量化表、下载信息、来源谱系
  • https://huggingface.co/ModelsLab/omnivoice-singing — 模型卡、许可证、使用示例、训练数据
  • https://huggingface.co/k2-fsa/OmniVoice — 上游模型卡、许可证、安全说明、arXiv 2604.00688
  • https://github.com/ServeurpersoCom/omnivoice.cpp — 运行时仓库、构建脚本、CLI 参数、许可证
  • https://pypi.org/project/omnivoice/ — PyPI 0.1.5,Apache-2.0,2026-04-28
  • backend/services/tts_backend.py — 现有 VoiceStudioBackend 模式(2026-05-18 阅读)
  • .planning/REQUIREMENTS.md — SPIKE-01、SPIKE-02、GGUF-01..06、SING-01..05(2026-05-18 阅读)
  • .planning/ROADMAP.md — Phase 4 成功标准(2026-05-18 阅读)
  • .planning/research/SUMMARY.md — Phase 2 SubprocessBackend 依赖项(2026-05-18 阅读)
  • .planning/research/STACK.md — 现有引擎的上下文(onnxruntime、huggingface_hub、transformers、supertonic)(2026-05-18 阅读)

次要来源(中等置信度)

  • omnivoice.cpp README — 缺少 Metal 构建路径(已核实内容本身,而非“无证据”;但要验证“不存在”本身很难)
  • 来自模型卡的 Q 变体 VRAM 大小(已在文中引用,但未在本研究中进行独立加载测试)

第三级来源(低置信度 — 标记为 Wave 1 验证)

  • quant_map.json 默认值中的量化质量阈值 — 没有公开基准;阈值基于通用 GGUF 量化质量知识,需通过 Wave 1 进行对比测试
  • SING-03 启发式准确度 — 现有工具包可实现,但尚未在 VoiceStudio 的特定配音语料库上进行基准测试

元数据

置信度细分:

  • Spike 产物身份与许可证:高(HIGH) — 模型卡已在线核实,来源链已端到端追踪
  • 集成形态(SubprocessBackend + 子类模式):高(HIGH) — 符合现有项目模式和 Phase 2 契约
  • 跨平台支持:Linux/Windows/CUDA + CPU 为高(HIGH);macOS Apple Silicon Metal 为中等(MEDIUM)(假设 A1)
  • 性能 / 延迟:低(LOW) — 没有公开基准;必须在 Wave 1 中验证
  • SING-03 启发式质量:中等(MEDIUM) — 工具包可用,准确度未做基准测试
  • 量化质量阈值默认值:中等(MEDIUM) — 基于已知信息的估算结果,待 Wave 1 验证

研究日期: 2026-05-18
有效期至: 2026-06-17(30 天;若之后再次查阅,需在 Phase 4 PR 开启前重新验证模型卡的 commit SHA 和 omnivoice.cpp 的 HEAD)

本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 om/VoiceStudio