RVC Next

与原版的一致性

RVC Next 重写了 Retrieval-based-Voice-Conversion-WebUI(下称“原版”,~/codeworkspace/Retrieval-based-Voice-Conversion-WebUI,提交 81eed5e)的编排、存储、进程控制和全部界面,但数值代码是移植的,不是重新发明的*

RVC Next 重写了 Retrieval-based-Voice-Conversion-WebUI(下称“原版”,~/code_workspace/Retrieval-based-Voice-Conversion-WebUI,提交 81eed5e)的编排、存储、进程控制和全部界面,但数值代码是移植的,不是重新发明的:用户已有的音色必须听起来和在原版里一样。本页说明移植的规则、用什么来证明一致(golden 测试)、哪些行为是有意改变的,以及维护者对照原版时需要知道的原版结构。

原版是一个 2026 年的重度修改分支,不是上游:它用 transformers 的 HuBERT 替换了 fairseq,用 PyMSS 替换了 UVR5,并加入了 CUDA Graph 加速、多说话人训练、命令行和 VST 插件。下文所说的“原版”都指这个分支。

移植规则

照搬:以下文件从原版复制,只修正导入路径,保持原版的形状,以便逐行 diff:

rvc-next原版
engine/models/{synthesizer,modules,attentions,commons,transforms}.pyinfer/module/*.py(四个合成器类和两种判别器)
engine/f0/rmvpe.pyinfer/rmvpe.py
engine/audio/denoise.pytools/torchgate/(TorchGate)
engine/graph.pytools/cuda_graph.py(只有开关从环境变量改为进程级设置)
engine/train/{slicer,losses,mel}.pytrain/dataset/slicer2.py、train/losses.py、train/mel_processing.py
engine/train/dataset.pytrain/data_utils.py

逐行移植到新接口:管线的数学一行不改,但包在新的函数和类里——离线转换(engine/convert/offline.py ← infer/vc/pipeline.py)、流式转换(engine/stream/engine.py ← infer/rtrvc.py 加 realtime_gui.py 的 start_vc/audio_callback)、训练各阶段(engine/train/* ← train/*.py、train/dataset/*.py、tools/multispeaker.py、webui.py 的文件列表代码)、检查点工具(engine/models/{checkpoint,merge,extract}.py ← train/process_ckpt.py)、GPU 规则(engine/runtime.py ← configs/config.py)、分离模型表(engine/separate/ ← tools/pymss_webui.py)。

不移植:Gradio 处理函数、实时 GUI 的回调和控件、进程启动方式(Popen(cmd, shell=True) 加 % 格式化的命令行)、中文状态字符串、返回 Gradio 更新字典的引擎接口。

为了让照搬的文件能与原版对比,pyproject.toml 把 engine/models/{attentions,commons,modules,transforms,synthesizer}.py 和 engine/f0/rmvpe.py 排除在 ruff format 和 ty 之外,并给它们以及 engine/audio/denoise.py、engine/train/{dataset,losses,mel,slicer}.py 按文件忽略 B、SIM、UP、E402、E712、E741、F841 等规则(见开发环境)。修改这些文件时保持原版风格;修 bug 时在 golden 测试里确认数值没变。

golden 测试

golden 测试在同一台机器上同时运行原版和 rvc-next,对比输出。两边都在 CPU、fp32、相同随机种子、相同的 16 kHz 输入(只解码一次)上运行。

离线:scripts/golden.py

python scripts/golden.py --original ~/code_workspace/Retrieval-based-Voice-Conversion-WebUI \
    --assets <资源目录> --voice a.pth [--voice b.pth] --clip speech.wav [--clip …] \
    [--f0 pm --f0 rmvpe --f0 fcpe] [--pitch 2] [--speaker-id 1] [--index x.index --index-rate 0.75]

scripts/golden.py 把原版 checkout 放到 sys.path 上,只导入 infer.hubert、infer.vc.pipeline 和 infer.module.models(不加载任何界面),用原版的 CPU 分块参数(x_pad/x_query/x_center/x_max = 1/6/38/41)构造 Pipeline;rvc-next 一侧用 Runtime(device="cpu", precision="fp32") 和 OfflineConverter。每个 (音色, 片段, F0 方法) 输出一行:样本差的 RMS(−1..1 刻度)以及是否逐位相同。容差 TOLERANCE = 1e-4,超出则退出码 1。不给 --f0 时只比 pm 和 rmvpe。

pytest:tests/golden/

两个测试都带 golden 标记,默认不选(addopts = "-m 'not live and not golden and not assets'"),用 pytest -m golden 运行。环境变量:

变量含义
RVC_NEXT_GOLDEN_ORIGINAL原版 checkout,默认 ~/code_workspace/Retrieval-based-Voice-Conversion-WebUI
RVC_NEXT_TEST_ASSETS原版布局的资源目录(hubert_base/、rmvpe/,离线测试还需 fcpe/fcpe_c_v001.pt)
RVC_NEXT_GOLDEN_VOICES用 os.pathsep 分隔的 .pth 列表
RVC_NEXT_GOLDEN_CLIP一个音频文件
  • test_offline_golden.py:通过 scripts/golden.py 对每个音色跑 pm、rmvpe、fcpe,移调 2、说话人 1;要求全部在 1e-4 以内且逐位相同。原版的 FCPE 依赖 torchfcpe,所以测试 importorskip("torchfcpe")——torchfcpe 要装在运行测试的环境里,供原版一侧使用,rvc-next 自己不依赖它。
  • test_stream_golden.py:原版的 start_vc/audio_callback 写在 GUI 的 __main__ 块里无法导入,测试里有一份去掉 GUI 的拷贝(OriginalStream),驱动原版的 rtrvc.RVC;rvc-next 一侧是 StreamEngine。对每个音色逐块(最多 12 块)比较,每块前用相同种子。三组参数覆盖 48k/40k、RMVPE/pm、移调、共振峰 1.0、输入输出降噪和门限。原版实时代码导入 torchaudio,所以测试 importorskip("torchaudio")。

缺少原版 checkout、资源或所需的包时,测试跳过而不是失败(CI 里没有原版的这些依赖)。

已经证明的

按照 AGENTS.md 的记录,CPU fp32 下:

  • 离线,pm 和 RMVPE:v1 40k、v2 40k、v2 40k 无音高、v2 48k 四类音色,在语音、静音和一段超过分块阈值的 70 秒片段上,带移调和说话人 id,输出逐位相同;FCPE 同样逐位相同。
  • 流式:StreamEngine 与 rtrvc.RVC.infer 加实时 GUI 回调数学(门限、输入输出 TorchGate、共振峰、RMS 混合、SOLA)逐块逐位相同。

模型必须在设置种子之前构建

HuBERT、RMVPE 和 FCPE 的权重初始化(随后被检查点覆盖)会从全局随机数生成器取数,而原版在 Pipeline 内部才构建 RMVPE/FCPE。如果一边先播种再建模型、另一边相反,两边后续的随机序列就错开了(例如 NSF 的噪声源),结果不再逐位相同。所以 golden 代码先 runtime.hubert()、runtime.f0(method),原版一侧也预先构建 RMVPE/FCPE 塞进 pipeline.model_rmvpe/model_fcpe,然后才 torch.manual_seed()。新增 golden 用例时保持这个顺序。

有意的行为变化

每一项都在原版代码里找到了原因;默认参数下,影响数值的变化都不改变 golden 结果。

变化原版rvc-next原因
切片保留每一片的尾部train/preprocess.py 里最后的 norm_write(tmp_audio…) 在 for audio in self.slicer.slice(audio) 循环之外,只写最后一段的尾部(81eed5e 引入的回归,5d47da1 时在循环内)PreProcess.slices() 在循环内收集尾部原版每段切片的尾部都被悄悄丢出训练集
Live 使用说话人infer/rtrvc.py 中 sid 写死为 0用 VoiceParams.speaker_id多说话人音色在实时里可以选人
索引按数据重建train/train_index.py 只要有任何 added_*.index 就跳过按特征文件指纹(index_fingerprint.json)判断换了数据重新训练时原版仍用旧索引
索引只加载一次infer/vc/pipeline.py 每个文件都 faiss.read_index + reconstruct_nRuntime.index() 按路径和修改时间缓存批量转换慢
处处都有 pm、rmvpe、fcpe原版 CLI 只接受 pm 和 rmvpe,推理界面有 fcpe界面、命令行、API、Live 都提供这三种,另有 CREPE 和 SwiftF0(来自 Applio 提供的算法);训练仍只有 pm 和 rmvpe同一个参数在各处行为一致
HuBERT 归一化训练特征提取在 do_normalize 为真时 layer_norm,推理从不一个函数 features/hubert.normalize_input,训练和推理共用真实资源 do_normalize: false,所以两边都不归一化,数值不变
离线共振峰只有实时有离线用实时的方法(key − formant 移调,生成 ceil(n·2^(f/12)) 帧并缩放 NSF 音高,再重采样回来)同一个参数在 Convert 和 Live 里一样;formant = 0 时逐位相同
任意路径实时 GUI 拒绝含非 ASCII 字符的路径;clean_path 剥引号操作系统接受的路径都可以—
推理不在音频回调里实时 GUI 在 sounddevice 回调里做整个转换回调只搬数据,处理线程运行引擎避免爆音,回调不该阻塞
改块长不停流重载改块长、交叉淡化、上下文都要停流重建缓冲,音色保持加载—
训练续跑恢复后重复已保存的那一轮从下一轮继续—
mp3/m4a 输出wav2 不改采样率,40k 模型导出 mp3/m4a 失败升到编码器支持的最近采样率(40 kHz → 44.1 kHz)—

另有几处不是行为变化、但对比时会看到的差异:

  • 离线输出最多短两个 10 ms 帧——这是原版逐块取整的结果,保留它才能逐位一致。
  • transformers 5:原版钉在 4.50 以下;rvc-next 需要 transformers>=5,<6(4.x 要求 huggingface_hub < 1)。HuBERT 各层输出不变,golden 运行逐位相同。
  • 索引 k-means 用 faiss 而不是 scikit-learn 的 MiniBatchKMeans(只在超过 20 万个向量时触发),少一个依赖,作用相同但数值不同。
  • CUDA Graph 开关:原版环境变量 RVC_CUDA_GRAPH,rvc-next 是设置 compute.cuda_graph_offline(默认关)和 compute.cuda_graph_live(默认开);缓存容量变量改名为 RVC_NEXT_CUDA_GRAPH_MAX_CACHE。golden 代码运行前会清掉 RVC_CUDA_GRAPH。

torchaudio 和 torchfcpe 的移植

原版通过 torchaudio 做实时重采样,通过 torchfcpe 做 FCPE。torchaudio 的最后一个版本是 2.11,而且不声明对 torch 的依赖,无约束的 torch 会解析到它无法加载的版本;torchfcpe 又会拉入 torchaudio。所以 rvc-next 把用到的部分移植进来,不再依赖这两个包:

移植来源用在证明
engine/audio/sinc_resample.py:Resample、sinc_resample_kernel、apply_sinc_resample_kerneltorchaudio 2.11 的 transforms.Resample 与 functional._get_sinc_resample_kernel/_apply_sinc_resample_kernel(BSD-2,许可证全文在文件头)流式引擎的输入→16 kHz、音色→设备采样率、两处共振峰重采样、FCPE 的非 16 kHz 输入tests/engine/test_sinc_resample.py:在 rvc-next 实际用到的采样率对上(48000→16000、44100→16000、40000→48000、471→400、504→480 等),核与输出与 torchaudio torch.equal;Kaiser 窗也相同
engine/f0/fcpe_model.py:Wav2Mel、纯卷积的 CFNaiveMelPE、解码器、load_fcpetorchfcpe(FCPE,MIT;conformer 来自 Diffusion-SVC)fcpe F0 方法tests/engine/test_fcpe_model.py:mel 与 F0 在 argmax 和 local_argmax 两种解码下与 torchfcpe 逐位相同(清音帧两边都是 NaN);内置权重与 torchfcpe 附带的一致

两处改动都只涉及类型、异常类型和去掉弃用名字,算术逐步不变。torchaudio/torchfcpe 不安装时,这些对比测试 importorskip 跳过,其余测试(长度、恒等、频率保持、非法参数、从运行时创建 provider)照常运行。FCPE 的权重 fcpe_c_v001.pt 作为 fcpe 资源下载,来源见资源与模型仓库。许可证见仓库根目录的 NOTICE。

原版代码库速览

对照原版排查问题时需要的地图。

入口

入口是什么在 rvc-next 中
webui.py(约 2,800 行)Gradio 3.14 应用,端口 7865;所有处理函数在一个文件里Web UI + API
realtime_gui.pyFreeSimpleGUI 桌面实时变声器(sounddevice)Live 页面与 workers.live
infer/cli.py离线转换命令行rvc-next convert
train/*.py、train/dataset/*.py每个训练阶段一个脚本,位置参数,由 WebUI 以 shell 子进程启动并跟踪日志workers.train 与训练服务
tools/pymss/cli.py内置的 PyMSS 命令行与 HTTP 服务器(RVC 本身不用)不提供;pymss 作为依赖
RVCRealtimeVST/Windows VST2/VST3 插件(iPlug2)驱动一个 Python worker未实现(计划在同一个 Live worker 上做适配器)

每个入口都从共享的 infer/ 自己构建引擎;实时 DSP 链在 realtime_gui.py、infer/rtrvc.py 和 VST worker 里有三份,F0 后处理在 pipeline、rtrvc 和 extract_f0 里各一份,PyMSS 既内置又作为 pip 包(tools/pymss_webui.py 把 tools/ 放在 sys.path 最前,内置版本生效)。rvc-next 把这些合成一份。

关键实现位置

功能原版位置要点
GPU 规则configs/config.py:get_device_dtype_sm导入时执行;Config 单例解析 argparse
离线管线infer/vc/modules.py:VC → infer/vc/pipeline.py:Pipeline48 Hz 高通、反射填充、能量最低点分块、整段 F0、逐块特征/检索/protect/合成
实时infer/rtrvc.py:RVC.infer + realtime_gui.py 回调回调里完成全部转换;skip_head/return_length;1024 帧音高缓存
CUDA Graphtools/cuda_graph.py离线默认关,实时开,训练关
训练调度webui.py 约 723–1530 行全局 TRAIN_TASK 加锁,一次一个阶段;每秒跟踪 logs/<exp>/*.log
训练循环train/train.py单卡无 DDP,多卡 DDP over gloo;mel L1 ×45、KL、特征匹配、LSGAN;AdamW 指数衰减;以 os._exit(0) 结束
多说话人tools/multispeaker.pyName_ID_Repeat 文件夹或分页表格;spk_embed_dim 固定 110;每个说话人一个 _spkid<N>.index;推理时 get_index_path_from_model 按名字打分配索引
分离tools/pymss_webui.py五个硬编码模型;每次运行一个子进程,JSON 请求文件 + JSON 行事件(rvc-next 的 worker 协议沿用了它);DirectML fp16 失败以退出码 75 用 fp32 重跑

必须继续读取的格式

格式细节为什么
小模型 .pthweight(fp16)、18 项位置 config、info、sr、f0、version、可选 speaker_info用户已有的全部模型;rvc-next 写出的模型也能在原版里用
faiss 索引added_IVF*_Flat_nprobe_*_<exp>_<版本>[_spkid<N>].index(版本为 v1 或 v2);trained 路径被换成 added已有索引
底模assets/pretrained{,_v2}/{f0,}{G,D}{32k,40k,48k}.pth;v2 40k 用 configs/v1/40k.json微调
实验文件夹0_gt_wavs … 3_feature768、filelist.txt 行格式、config.json、multispeaker_manifest.json导入旧实验(engine/train/experiment.py:read_legacy)
资源布局assets/hubert_base/(Hugging Face 格式)、assets/rmvpe/rmvpe.pt 与 rmvpe.onnx、assets/pymss_weights/、logs/mute/paths.assets_dir 可以直接指向原版的 assets/,不必重复下载

原版里发现的问题与去向

问题去向
切片丢尾(回归)已修,见上表
实时说话人固定为 0已修
有旧索引就跳过构建按指纹重建
每个文件重读索引运行时缓存
各处 F0 选项不一致统一
训练与推理归一化不一致一个函数
老旧 Web 栈(Gradio 3.14、pydantic 1、FastAPI < 0.100)FastAPI + Vue 3,pydantic v2
阶段以 shell=True 加 % 格式化命令运行,路径特殊时脆弱worker 子进程读 JSON 请求文件,用 sys.executable 启动
引擎返回 Gradio 更新字典和中文状态引擎不依赖任何界面层
代码多处拷贝每样只有一份
实时推理在音频回调里;VST 欠载后不重新同步、5 秒超时才发现 worker 死掉、每个实例一个 CUDA 上下文处理线程 + 环形缓冲 + 看门狗;VST 未实现

本页目录