Mizuoto

开发约定

为 Mizuoto 写代码要遵守的约定:代码风格、分层规则、原生代码的规则、结果一致性规则,新增命令、路由、设置和引擎选项的步骤,测试要求,双语文档和提交信息的格式。

本页列出改动 Mizuoto 时必须遵守的约定。大多数约定都有测试或检查守着:python scripts/dev.py check 通过,是一个改动可以合并的最低要求。每条约定背后的设计见整体架构和设计参考与决策。

代码风格

  • Python ≥ 3.10,支持 3.10–3.13。ty 以 3.10 为目标检查类型,所以不要用 3.11 之后才有的语法和标准库(tomllib 要有 tomli 回退)。
  • ruff 与 ty 不锁版本,pyproject.toml 把 lint.select 逐条写出(E4、E7、E9、F、I、UP、B、SIM、RUF100、ANN),所以升级 ruff 不会悄悄改变规则集。行宽 180,缩进 4。ty 以 error-on-warning 运行。python scripts/dev.py format 格式化并做安全的自动修复,lint 和 typecheck-py 只检查。
  • 每个函数都有类型标注(mizuoto/ 和 packages/native/ 下;测试和脚本除外)。Any 只用在值确实可以是任何东西的地方(JSON)。
  • 跨越 API 边界的模型都继承 Record(mizuoto/core/record.py,pydantic v2,json_schema_serialization_defaults_required):设置、请求、响应、事件、--json 的输出都是。有默认值的字段在输出模式里是必填的,生成的 TypeScript 类型因此与服务器实际发送的一致。
  • 重依赖在函数内导入:核心里的 av、numpy 热路径,命令行里的 fastapi、uvicorn,以及 core/inference 里的所有模型代码。mizuoto version 和 mizuoto --help 因此保持快速,测试会检查这一点。
  • 有类注解里用到 list[...] 的类不定义名为 list 的方法(ty 会把这个名字解析成方法)。写成 list_models、list_voices、list_presets。
  • 标识符和时间用 mizuoto/core/clock.py:new_id(prefix)(毫秒时间戳的十六进制加 8 个随机十六进制字符,大致按创建时间排序)和 now_iso()(UTC,毫秒,以 Z 结尾,可按文本排序)。
  • 错误只用 mizuoto/core/errors.py 的类,错误码只在 mizuoto/core/codes.py。给用户的修复命令放在 detail["hint"],命令行会把它作为第二行打出,界面显示在错误下方。不要在路由或命令里自己拼 HTTP 状态码或退出码。
  • 日志用 mizuoto 日志记录器(logging.getLogger(__name__) 位于 mizuoto.* 之下);只有入口(mizuoto/logger.py 的 setup_logging)配置处理器。
  • 与上游库逐行对应、需要保持可比对的代码(mizuoto/nn/dsp/kaldi.py,torchaudio 的 fbank)按原样保留,在 pyproject.toml 里单独豁免 lint 规则,而不是改写。

分层规则

tests/test_architecture.py 用 ast 检查每个模块的导入,规则表见整体架构。要点:

  • core、api、cli 永远不导入 torch、torchaudio、transformers、onnxruntime 或需要接受许可的引擎包,函数内也不行。core 只在 core/inference 的函数内部触及 runtime、nn、engines 和 training。
  • 路由和命令很薄:解析输入、调用一个核心方法、整理输出。每个操作只有一个核心方法,所以 API 的 JSON 与命令行的 --json 形状相同。业务逻辑不进 api/ 和 cli/。
  • 引擎只导入 runtime、nn、text、training、core.codes、core.inference.tasks 和自己的包,从不导入另一个引擎。两个引擎共用的代码放进 nn 或 runtime。
  • core/codes.py、core/jobs/events.py、core/jobs/jsonl.py、core/remote/frames.py 和 core/inference/tasks.py 只用标准库:训练阶段、远程帧和引擎都要用它们。
  • 新加一个第三方依赖,要同时把它加到 pyproject.toml 和该层在 tests/test_architecture.py 里允许的列表中,并说明它为什么必要(见发布)。

原生代码的规则

引擎、训练器、运行时和服务器共用一个进程,所以 runtime、nn、text、engines 和 packages/native 下的代码要守这些规则(细节见运行时):

  1. 不改动进程级状态。 不给全局随机数生成器播种,不改 os.environ、工作目录或 sys.path,不调用 logging.basicConfig、torch.set_default_dtype、torch.use_deterministic_algorithms、torch.set_float32_matmul_precision,不给 torch.backends.* 或 torch._inductor.config 赋值,不用 sdpa_kernel(它对所有线程切换 SDPA 的内核)。compute.fast 的那几个开关只在 mizuoto/runtime/accel/features.py 里设置,由主机和训练阶段调用。
  2. 随机数来自请求。 用 ctx.generator_for(device) 和 ctx.rng,每个随机操作都显式传入生成器(torch.randn(..., generator=g)),并按上游的顺序、形状和设备抽取。randn_like 等不接受生成器的调用改写成等价的显式形式。上游在构建模型时从全局生成器抽过随机数的,用私有生成器重放。
  3. 加载不抽随机数。 模块用 runtime.ops 的层构建(ops.default 跳过随机初始化),检查点严格加载(缺少或多出的键都是错误,除非引擎明确列为可忽略)。
  4. 加载在推理模式之外进行,参数保持 nn.Module 创建时的样子(不调用 requires_grad_(False))。CPU 上有两个会改变结果的内核陷阱:对非连续的三维输入,F.linear 在权重 requires_grad 或权重是推理张量(在 torch.inference_mode() 里创建)时会选用另一个内核,即使处在 no_grad 或推理模式下。上游的权重是需要梯度的普通参数,所以原生引擎也这样保留;请求仍在推理模式里运行。
  5. 注意力都经过 accel.attention(q, k, v, site=…),不直接调用 SDPA 或它的 aten 内核,这样 compute.attention 才能切换后端(见注意力加速)。
  6. 不下载任何东西。 引擎用的文件都是模型文件或共享资源,由 ModelService 下载并通过 LoadSpec 交给引擎。
  7. 模块级缓存要以完整输入为键(包括每个参数、dtype 和设备),或者干脆每次调用时构建。只按部分参数作键的全局缓存,会让同一进程里的第二种配置用上第一种的结果。
  8. 按阶段放置组件,按请求估算显存。 引擎用 ctx.use / ctx.run 把每个阶段需要的组件放上设备,在模块 forward 之外读取权重的模块要标成 fixed;需要 float64 的模块经 DeviceService.float64_device 放置。见显存管理。
  9. 可取消。 每个循环(自回归 token、ODE 步、分段、束搜索步)里调用 ctx.check_cancel()。Cancelled 继承自 BaseException,不要用宽泛的 except BaseException 吞掉它。

结果一致性

Mizuoto 的每个引擎都是对上游项目的移植,必须给出与上游相同的结果:在 CPU 上以 fp32、相同种子运行时,音频逐采样相同(训练器则是损失、学习率和保存的权重逐字节相同)。这条规则决定了很多写法:

  • 先复现,再修正。 上游的怪癖(采样顺序、填充方式、对齐的边界情况)先原样复现,用对照工具证实一致;确有必要修正时,作为单独的改动,并记录偏差及原因。
  • 偏差要记录。 每个与上游不同的地方(为了跨平台、为了不改全局状态、因为上游行为依赖线程时序等)写进该引擎的说明和设计记录 AGENTS.md,附上对照数字。
  • 提速不改变结果。 性能工作(CUDA 图、同步消除、编译、内存流式加载)必须得到相同的 token、相同的音频;每一轮都用 scripts/parity/perf.py 和 train_perf.py 与上一轮对照(见性能工作)。确实会改变比特的优化(如 Qwen3-TTS 的 fused_projections、compute.kv_cache = static)只能是需要用户主动开启的选项,并在输出的参数里注明。
  • GPU 上降精度运行时不要求逐比特相同,以模块级误差和统计指标判断。各层级的容差见测试。

新增东西的步骤

要新增步骤
一条命令在 mizuoto/cli/commands/ 写普通函数并提供 --json,在 cli/app.py 的 get_app() 中注册(名字和帮助写在注册处),加进 tests/cli/test_cli.py 的 EXPECTED_TREE。见新增一条命令
一个路由核心方法(Record 进出)→ api/routers/ 里带 operation_id 的路由 → python scripts/dev.py typegen 并提交 schema.d.ts → tests/api/test_api.py。见新增一个路由
一个事件在 core/events/models.py 加 EventBase 子类(__event_name__),它自动进入 OpenAPI 模式;重新生成类型,并在界面的 api/socket.ts 里处理它
一个设置项在 core/settings/models.py 对应的组里加字段,带默认值和说明文档字符串;需要界面时在 Settings 界面加控件、两种语言的文字和 typegen;在配置页面的两个语言版本里写明。engines.<id>.* 的改动会让该引擎的模型卸载,下次按新设置加载
一个引擎选项在清单里加 [options.<名字>](或加载选项 [load_options.<名字>]),并在引擎的 OPTION_SCHEMA 里加同样的项,测试要求两者完全一致。见接入引擎
一种任务在服务的构造函数里 jobs.register(kind, factory),工厂返回带资源(engine:<引擎>:<设备>、gpu:<设备>、cpu、io…)的 JobSpec;结束时会影响哪些列表,就在界面 socket.ts 的 AFFECTS 里登记
一个引擎或训练器见接入引擎和训练器
一个界面见 Web UI:只从 @/ui 取组件、只用令牌写样式、两种语言的文字同时加

测试要求

  • 每个改动都带测试,并让 python scripts/dev.py check(lint、typecheck-py、test-py、test-web、typegen-check)通过。CI 在 Linux、Windows、macOS 和 Python 3.10–3.13 上运行同样的测试,所以注意路径、编码和换行在 Windows 上的行为。
  • 核心的行为用 dummy 引擎测(不需要 torch 和模型);外部网络用 httpx.MockTransport 替代;测试用 make_services 得到隔离的数据目录和空环境变量。
  • 改动引擎或训练器:更新 tests/engines_native/ 的小模型测试(包括模拟设备上的流式加载检查),用真实模型跑契约套件,并用对照工具与上游比较。
  • API 改了就重新生成类型并提交,否则 typegen-check 失败。界面改动要通过规则测试(导入、令牌、布局、浮层、i18n 键一致)。

文档

  • 两种语言。 文档站的每个页面都有 <slug>.mdx(简体中文,默认语言)和 <slug>.en.mdx(英文),结构逐节对应;侧边栏是 meta.json 和 meta.en.json,顺序相同。测试检查每个中文页面都有英文版。README 也有两份(README.md、README.zh-CN.md),界面文字在 en.ts 和 zh-CN.ts 里,键必须一致。
  • 改行为就改文档:用户可见的变化写进使用教程、命令行或进阶页面,开发者需要知道的写进开发文档。
  • 设计记录。 仓库根目录的 AGENTS.md 记录代码实际做了什么、守着哪些规则、为什么,以及长期有效的发现(例如为得到与上游相同的输出复现了哪些行为)。改了设计或得出了新发现,就同时更新它。
  • 中文页面沿用已有页面的术语(音色、合成、克隆、模型、引擎、共享资源、许可、任务……)。写清楚是什么、为什么,不写开发过程。

提交信息

提交信息采用 类型(范围): 摘要 的格式,摘要用英文小写开头的祈使句或名词短语,说明改动做了什么:

feat(voxcpm): VoxCPM as a built-in engine, with full and LoRA fine-tuning
fix(runtime): size working memory by the request, so models load whole when they fit
perf(cosyvoice): CosyVoice 2/3 sample on the device and decode as CUDA graphs
docs(site): the mirror_fetch and mirror_readme scripts
  • 类型:feat(新功能)、fix(修复)、perf(不改变结果的提速)、refactor(不改变行为的重构)、test、docs、build(构建、CI、Docker)、chore(杂项)。不兼容的改动在类型后加 !,如 refactor!:。
  • 范围是受影响的部分:runtime、webui、training、models、scripts、site,或引擎 id(qwen3tts、gptsovits 等);跨越多处时可以写多个(runtime,training)或省略。
  • 正文说明为什么这样做、对照或测量的结果,以及有意保留的行为。

本页目录