发布
Mizuoto 的依赖及其理由、支持的平台,以及发行包怎样构建和检查:各自发布的版本、wheel、冒烟测试、CI 与发布工作流、Docker 镜像、模型镜像和本文档站。
一次发布产出三个发行包:mizuoto(wheel 和 sdist,内含构建好的 Web UI,以及所有采用宽松许可证的引擎),以及两个需要接受许可的引擎的 wheel:mizuoto-fishspeech 和 mizuoto-indextts。三者都装进同一个环境,装在用户自选的 torch 之上,各自有版本号、各自检查、各自发布。本页讲它们怎样构建、检查并发布到 PyPI(由发布工作流用 twine 完成)。
依赖
Mizuoto 是一个环境:核心、API、命令行、Web UI 和每个引擎的依赖都写在 pyproject.toml 里,一次安装。每个新依赖都要回答:它在 CI 覆盖的每个平台(Linux x86_64、Windows x86_64、macOS arm64,CPython 3.10–3.13)上有没有 wheel,它会不会改变结果,它会不会改动进程级状态。
必需的依赖
| 用途 | 包 | 说明 |
|---|---|---|
| Web 与命令行 | fastapi(≥ 0.115.3)、starlette(≥ 0.40)、uvicorn、websockets、python-socketio、pydantic 2、httpx、typer(< 0.28)、rich、tomli-w、tomli(Python < 3.11)、send2trash | Starlette 0.40 起 FileResponse 支持 Range,播放器可以拖动;uvicorn 要有 websockets 才能处理 WebSocket;typer 锁上限,因为命令行用到它的私有模块 |
| 音频 | av(PyAV)、numpy | 解码、编码、重采样都用 PyAV,其余用 numpy;不调用 ffmpeg 或 sox 程序 |
| 运行时 | torch(≥ 2.8)、safetensors、soxr、tokenizers | torch 的构建由用户选(见下);soxr 是 librosa 调用的同一个重采样器;tokenizers 构建与 transformers 相同的 BPE |
| 文本 | pypinyin、rjieba、jieba、opencc、kaldifst、inflect、tiktoken、regex、wordsegment、budoux、ToJyutping、fasttext-predict、pyyaml | 很多版本是锁定的(如 pypinyin==0.55.0、opencc==1.4.2、inflect==7.3.1):引擎的输出依赖这些版本,换版本会改变音素和音频 |
需要接受许可的包各自带少量依赖:mizuoto-fishspeech 需要 tokenizers 和 safetensors,mizuoto-indextts 需要 sentencepiece。它们也写明自己直接导入、Mizuoto 已经带来的包(torch 和 numpy;mizuoto-indextts 还有 pyyaml 和 tiktoken),版本范围与 Mizuoto 相同,这样自己的依赖不靠 Mizuoto 的依赖成立。
可选附加依赖
| 附加依赖 | 包 | 作用 |
|---|---|---|
mizuoto[gptsovits-ja] | pyopenjtalk==0.4.1 | GPT-SoVITS 的日语前端;只发布源码包,安装时需要 CMake 和 C++ 编译器 |
mizuoto[gptsovits-ko] | python-mecab-ko==1.3.7、jamo==0.4.1 | GPT-SoVITS 的韩语前端 |
mizuoto-indextts[ja] | fugashi、unidic-lite | IndexTTS-2.5 的日语分词 |
缺少附加依赖时,相应语言的文本以 capability_not_supported 失败,提示里带安装命令;mizuoto doctor 也会列出各引擎缺哪些附加依赖。可选的注意力加速包(flash-attn、xFormers、SageAttention)不是附加依赖:它们要与用户的 torch 版本匹配,mizuoto doctor 给出保留现有 torch 的安装命令(--no-deps),见注意力加速。
不再需要的依赖
每个引擎都在仓库内移植,下面这些上游依赖都不再需要:
| 不再需要 | 由什么代替 |
|---|---|
| vLLM、TensorRT / TensorRT-LLM、FlashInfer、必需的 flash-attn | 自回归解码的同步消除与 CUDA 图、可选的 torch.compile、SDPA 与可选的注意力后端 |
| transformers(多个互不兼容的版本)、accelerate、huggingface_hub、peft、loralib | 仓库内的模块(Qwen 解码器、GPT-2、BERT、HuBERT、w2v-BERT、Whisper……)、tokenizers、仓库内的 LoRA |
| onnxruntime | ONNX 文件里的权重由仓库内的协议缓冲区读取器(runtime/convert/onnx.py)转换成 torch 模块,转换结果缓存在模型文件夹的 .mizuoto/ 下 |
| openai-whisper、faster-whisper(CTranslate2) | 仓库内的 Whisper、log-mel 与 tiktoken 词表(随包发布) |
| torchaudio、librosa、numba、resampy、scipy | mizuoto.nn.dsp,与这些库逐比特一致;soxr |
| descript-audiotools、descript-audio-codec | mizuoto/nn/codecs/dac.py |
| pynini、WeTextProcessing、wetext、modelscope | kaldifst 加上导出好的 FST(共享资源,或随包发布) |
| jieba_fast、g2p_en、nltk(以及它运行时下载的数据) | jieba;移植的 GRU 与词性标注器,数据随包发布 |
| hyperpyyaml、omegaconf、hydra | 当作数据读取的 YAML 与 dataclass 配置 |
| x-transformers、diffusers、conformer、Matcha-TTS、vocos、torchdiffeq、einops、ema-pytorch | 仓库内的代码 |
| Lightning、torchmetrics、datasets、webdataset、wandb、tensorboard、pyarrow、pyworld、deepspeed | 原生训练器 |
| pydub(及 Python 3.13 已移除的 audioop) | 保留整数语义的 numpy 移植 |
| sox、ffmpeg 程序、soundfile | PyAV;nn/dsp 里的 WAV、FLAC 读写和 libswresample 重采样器的移植 |
torch 与平台
torch 是依赖,但它的构建由用户先装:从 download.pytorch.org 与本机匹配的索引安装(CUDA、ROCm、XPU 或 CPU;Apple 芯片上 PyPI 的 wheel 带 MPS),再装 Mizuoto,安装 Mizuoto 不会替换它。mizuoto/core/compute/torch_index.py 知道 torch ≥ 2.8 发布了哪些构建(CUDA cu126–cu132、ROCm 6.4–7.2、XPU、CPU),选出驱动能运行的最新 CUDA 构建,否则 ROCm、XPU、MPS 或 CPU;mizuoto doctor 在已装的构建与硬件不符时打印安装命令。
| 设备 | 平台 |
|---|---|
| NVIDIA(CUDA) | Linux、Windows |
AMD(ROCm,torch 里也叫 cuda) | Linux |
| Intel(XPU) | Linux、Windows |
| Apple 芯片(MPS) | macOS arm64 |
| CPU | 全部 |
CI 在 Linux x86_64、Windows x86_64 和 macOS arm64 上,用 CPU 版 torch 测试 Python 3.10–3.13 的每个组合,并分别用 torch 2.8.0 和最新版测试。
ROCm、XPU 和 MPS
代码为这些设备准备了相应的路径(设备探测、没有 float64 的设备上把 float64 模块放在 CPU、MPS 上不编译、只在 CUDA 上用 CUDA 图和半精度),并在模拟的设备上测试过,但还没有在 AMD、Intel 或 Apple 的硬件上验证。
版本
每个发行包有自己的版本号,放在一个只含版本号的文件里;推送到 main 时改动了它,就发布这个发行包(见下文):
| 文件 | 谁的版本 | 标签 |
|---|---|---|
mizuoto/version.py | mizuoto(Web UI 的 package.json 手动跟随) | v<版本> |
packages/native/fishspeech/mizuoto_fishspeech/version.py | mizuoto-fishspeech | mizuoto-fishspeech-v<版本> |
packages/native/indextts/mizuoto_indextts/version.py | mizuoto-indextts | mizuoto-indextts-v<版本> |
需要接受许可的包在自己的 pyproject.toml 里写明它适用的 Mizuoto 版本:mizuoto>=0.2.0,<0.3。它的代码跑在 Mizuoto 的运行时上,它的清单(选项、模型目录、许可)随 mizuoto 发布,所以:
- 下限是包含该包所用一切的最旧 Mizuoto 版本。CI 用 PyPI 上的这个版本测试该包(
oldest-mizuoto作业);包开始用到更新的东西时提高下限,并先发布 Mizuoto。 - 上限在 Mizuoto 还是 0.x 时取下一个次版本。
check_gated_distribution(在tests/core/test_engines.py,由各包的test_<id>_package.py调用)在 Mizuoto 的版本超出范围时失败,所以 Mizuoto 发布越过上限时,也要提高范围并发布这两个包。 - 改动一个需要接受许可的引擎的选项,也会改动
mizuoto里它的清单:先发布带新清单的 Mizuoto,再发布把下限提高到该版本的包。
已安装的需要接受许可的引擎与当前 Mizuoto 不匹配时(范围不包含当前版本,或它的 OPTION_SCHEMA 与清单不一致),mizuoto doctor 报告为错误,并给出 pip install --upgrade mizuoto mizuoto-<id>;mizuoto version 列出每个发行包的版本。
Wheel:scripts/build_wheels.py
python scripts/dev.py wheels # all:mizuoto 和每个需要接受许可的引擎,各用自己的版本
python scripts/dev.py wheels host # 只构建 mizuoto
python scripts/dev.py wheels native indextts # 指定的需要接受许可的引擎
python scripts/build_wheels.py host --skip-web --out /wheels- host:在
mizuoto/webui中bun install --frozen-lockfile和bun run build(--skip-web沿用现有的dist/,Docker 的wheels阶段就是这样),检查dist/index.html存在,然后用uv build构建mizuoto的 sdist,再由它构建 wheel;构建所用的是mizuoto/、pyproject.toml和README.md的干净副本(没有__pycache__,没有残留的build/)。 - native:
packages/native/下的每个包(或指定的那些),复制时去掉__pycache__、*.egg-info、build和dist,用uv build --wheel构建。 - 默认输出到
dist/。脚本只用标准库加 uv 和 bun。
随 wheel 发布的包数据列在 pyproject.toml 的 [tool.setuptools.package-data] 里:构建好的界面、引擎清单、模型文件清单和许可全文、F5-TTS 的词表、GPT-SoVITS 文本前端的数据、Whisper 的词表。IndexTTS 的文本规范化 FST 随 mizuoto-indextts 发布。
冒烟测试:scripts/smoke_wheels.py
python scripts/dev.py smoke # 等同于 python scripts/smoke_wheels.py --dist dist
python scripts/smoke_wheels.py --dist dist --python 3.13 --torch torch==2.8.0
python scripts/smoke_wheels.py --dist dist --others none # 只装 dist/ 里的 wheel它像用户那样试用构建出的发行包。在源码目录之外的临时文件夹里,uv 新建一个环境(--python,默认 3.11),先从 --torch-index 装 torch(默认 CPU 构建;--torch 指定版本,默认 torch>=2.8),再一次性安装 --dist 里的 wheel,以及它缺少的发行包(来自 --index;--others index,默认),这次安装不得替换已装的 torch。由于每个发行包各自发布,一次发布要和用户手里其他已发布的包一起试:mizuoto 的 wheel 配上两个需要接受许可的引擎已发布的包,需要接受许可的引擎的 wheel 配上其范围允许的最新已发布 Mizuoto。已发布的那两个包不允许的 Mizuoto 版本会在这里失败。装上的版本必须就是这些 wheel 的版本。uv 不读取本机配置(--no-config,不带 UV_*/PIP_* 变量)。然后在临时数据目录里:
mizuoto version、mizuoto env vars和mizuoto doctor:每个引擎都必须可用;- 导入每个引擎的实现和训练器(
get_engine),导入mizuoto和需要接受许可的包的每个模块,确认由源码构建的发行包里与代码放在一起的每个数据文件都装上了(引擎运行时会读的那些还要真正读一遍); - 用 dummy 引擎执行一次
mizuoto speak; - 服务器:
/api/v1/app/health、界面,以及经 API 用 dummy 引擎合成一次。
CI 与发布工作流
.github/workflows/ci.yml 按每次推送和拉取请求改动的内容检查:Mizuoto、某个需要接受许可的包、Web UI、文档站(见测试)。
.github/workflows/release.yml 在推送到 main 且改动了某个版本文件(mizuoto/version.py、packages/native/*/mizuoto_*/version.py)时、推送 v* 或 mizuoto-*-v* 标签时(GitHub 对标签不应用路径过滤)以及手动触发时运行(输入 packages:all、mizuoto、mizuoto-fishspeech 或 mizuoto-indextts)。同一引用同时只运行一次发布,且不会被取消。依赖和 CI 一样用 uv 安装。
plan:python scripts/ci.py release决定这次发布什么。推送发布它改动了版本文件的每个发行包(文件列表来自 compare API);标签发布它指明的那个,且必须等于其版本(Mizuoto 0.2.0 对应v0.2.0,mizuoto-indextts 0.2.1 对应mizuoto-indextts-v0.2.1);手动触发发布输入指定的。checks:把ci.yml本身作为可复用工作流调用,检查这次推送改动的内容(版本文件检查它所属的发行包;标签或手动触发检查全部)。CI 自己对同一次推送到main的运行会跳过它的作业,所以不会重复运行。build(Ubuntu、Python 3.11、uv;构建 Mizuoto 的 Web UI 时还有 bun):只构建要发布的,build_wheels.py host和/或build_wheels.py native <id>…;twine check dist/*;dist/作为dist产物上传。smoke(Ubuntu,Python 3.10 和 3.13):下载产物,运行smoke_wheels.py --dist dist --python <版本> --others index,让这次发布与 PyPI 上已发布的其他发行包一起试用。publish:在pypi环境中(可以要求审批),用TWINE_PASSWORD密钥(PyPI API 令牌,用户名__token__)先twine upload --skip-existingMizuoto,再上传需要接受许可的 wheel,所以需要新 Mizuoto 的包不会先于它出现。PyPI 上已有的版本会被跳过。github-release(仅标签):以标签命名的 GitHub 发布,附上该发行包的文件,说明从同一发行包的上一个标签起自动生成;需要接受许可的引擎的发布不标记为 latest。
所以发布一个发行包的通常流程是:改它的版本文件,推送到 main(发布工作流把它发布出去),再推送它的标签(v<版本> 或 mizuoto-<id>-v<版本>)生成 GitHub 发布(它上传时会发现这些文件已在 PyPI 上)。一次推送改了几个版本文件,就一起发布,Mizuoto 先发。
Docker
仓库根目录的 Dockerfile 构建一个镜像:服务器、它的 Web UI 和所有引擎,带 GPT-SoVITS 的日语和韩语前端(GATED=1 时还有 IndexTTS-2.5 的日语)。模型放在 /data 卷里。
docker build -t mizuoto . # torch 的 CUDA 12.8 构建
docker build -t mizuoto:cpu --build-arg TORCH_INDEX=https://download.pytorch.org/whl/cpu .
docker build -t mizuoto --build-arg GATED=1 . # 带 Fish Speech 和 IndexTTS
docker run --gpus all -p 7869:7869 -v mizuoto-data:/data -e MIZUOTO_SERVER__ACCESS_TOKEN=change-me mizuoto| 构建参数 | 默认值 | 含义 |
|---|---|---|
PYTHON | 3.11 | wheels 阶段和镜像的基础镜像 python:<版本>-slim |
TORCH_INDEX | https://download.pytorch.org/whl/cu128 | torch 的来源:CUDA(cu126 … cu132)、ROCm(rocm6.4 … rocm7.2)或 cpu 索引 |
TORCH | torch>=2.8 | 装哪个 torch |
GATED | 0 | 设为 1 时同时构建并安装需要接受许可的引擎(它们的许可仍需接受) |
| 阶段 | 内容 |
|---|---|
web | oven/bun:1:bun install --frozen-lockfile、bun run build |
wheels | 带编译器和 CMake 的 python:<PYTHON>-slim:构建 pyopenjtalk==0.4.1 的 wheel(它只发布源码包)、build_wheels.py host --skip-web,GATED=1 时再 build_wheels.py native |
| 镜像本身 | python:<PYTHON>-slim:先从 TORCH_INDEX 装 torch,再装 wheel 及 mizuoto[gptsovits-ja,gptsovits-ko](以及 mizuoto-indextts[ja]),其余依赖来自 PyPI;MIZUOTO_DATA_DIR=/data,/data 卷,端口 7869,对 /api/v1/app/health 的健康检查 |
镜像里不装任何系统包:PyAV、torch 和各文本库的原生代码都在它们的 wheel 里。镜像里没有 C 编译器,所以 compute.compile 以即时执行方式运行(只警告一次)。镜像的用法见 Docker。
模型镜像:scripts/mirror_models.py
downloads.repository = "mizuoto-model" 让 Mizuoto 先从它的镜像仓库(downloads.mirror_repo,默认 licyk/mizuoto-model,Hugging Face 与 ModelScope 同名)下载,镜像缺的文件再从各模型的官方仓库补齐,所以不完整的镜像也能用。镜像里的文件按 Mizuoto 存放的方式排列:模型在 <engine>/<model>/<path>,共享资源在 _assets/<engine>/<asset>/<path>。
python scripts/mirror_models.py build OUT [id…] # 从模型目录排出镜像
python scripts/mirror_models.py upload OUT --repo licyk/mizuoto-model [--modelscope]
python scripts/mirror_models.py check --repo licyk/mizuoto-modelbuild把已安装的目录模型和共享资源排到OUT下(尽量用硬链接),每个文件对照固定的大小和 SHA-256 检查(--no-verify只查大小),并写出MIRROR.json(每个文件的路径、大小、SHA-256 和官方来源)与 README。压缩包部分(unpack)解压后不保留,所以只从官方下载。需要接受许可的引擎默认不包含,除非--gated。upload用hf upload-large-folder发布(需要hf auth login),--modelscope时再用modelscope upload。check用 HEAD 请求列出已发布的镜像缺少哪些目录文件。
发布镜像是镜像维护者的步骤,权重保留各自的许可。
文档站
本站在 site/,基于 fumadocs(TanStack Start + Vite),用 bun 构建为静态文件(.output/public)。.github/workflows/site.yml 把它发布到 GitHub Pages:推送到 main 且改动了 site/ 时(或手动触发),它运行站点的检查,以 VITE_BASE_PATH=/Mizuoto/ 构建(Pages 在 https://licyk.github.io/Mizuoto/ 下提供;路由和搜索索引跟随 Vite 的 base),加上 .nojekyll,再用 GITEE_RSA_PRIVATE_KEY 密钥(对本仓库有写权限的 SSH 密钥)经 SSH 把输出强制推送到 gh-pages 分支。仓库的 Pages 设置以该分支为来源。site/vercel.json 可直接用于在根路径部署到 Vercel。
python scripts/dev.py site-install # 在 site/ 中 bun install
python scripts/dev.py site-dev # 本地预览,热更新
python scripts/dev.py site # vite build,再运行 scripts/prepare-static-output.mjs
python scripts/dev.py test-site # vitest,然后 tsc --noEmitCI 的 site 作业在 site/ 中依次运行 bun install --frozen-lockfile、bun run check、bun run test 和 bun run build,只在 site/ 有改动时运行。
- 页面在
site/content/docs/:<slug>.mdx是简体中文(默认语言zh-CN),<slug>.en.mdx是英文,两者结构相同。侧边栏是meta.json与meta.en.json,以---标题---分组。 - 页首的 frontmatter 有
title、description和icon(Lucide 图标名)。可用的 MDX 组件见site/src/components/mdx.tsx:Cards/Card、Callout、Tabs/Tab、Steps/Step、Accordions/Accordion和Mermaid(chart属性,在浏览器中从 CDN 加载 Mermaid 渲染,随站点的明暗主题)。 prepare-static-output.mjs确认meta.json中的每个页面都生成了docs/<slug>/index.html,并把_shell.html复制为404.html。src/lib/i18n-content.test.ts要求每个中文页面都有英文版,两个侧边栏的页面顺序一致。