安装检查、平台适配与 ZLUDA
本页说明 Applio 启动时如何创建基础配置、应用平台适配、检查安装路径,并根据 GPU 后端加载 ZLUDA/AMD 兼容性补丁。内容聚焦启动前的运行环境准备与 PyTorch 运行时改写,不涵盖推理、训练、模型下载或各业务 Tab 的内部实现。
Purpose and Scope
本页覆盖以下端到端路径:
app.py如何确定工作目录、创建assets/config.json,并在导入 UI 前调用platform_config()。- macOS Apple Silicon 与 Windows 的平台专用环境变量处理。
- Windows 下对工作目录所在磁盘、OneDrive、空格和非 ASCII 路径的安装检查。
rvc.lib.zluda如何在检测到 ZLUDA 或 AMD GPU 时改写部分 PyTorch 运算。- 启动阶段如何调用
run_prerequisites_script()与check_installation()。
安装脚本本身、模型下载流程、训练/推理业务和实时 Tab 的完整实现属于其他功能边界;本页只描述它们在启动检查链路中的接入点。源码没有提供独立的“平台选择”配置项,因此不要将这些适配逻辑误解为可通过 UI 任意切换的后端策略。
Overview
Applio 将环境准备放在应用主入口的导入阶段执行。入口先以当前工作目录为基准准备配置文件,再设置平台环境变量;随后导入 ZLUDA 兼容层,执行前置依赖准备,最后进行安装路径检查并继续构建 Gradio UI。这个顺序的设计意图是让后续模块在导入或创建模型前看到正确的环境状态。
其中有两类适配:
- 操作系统适配:
platform_config()只处理已明确实现的 macOS ARM64 和 Windows 分支。macOS ARM64 设置 OpenMP 线程数与 KMP 重复库兼容变量;Windows 根据assets/config.json中realtime.asio_enabled决定是否设置SD_ENABLE_ASIO。 - GPU/运行时适配:
rvc.lib.zluda在模块导入时检测 CUDA 设备名称。设备名以[ZLUDA]结尾时,替换特定形态的torch.stft;设备名包含AMD时,启动时基准测试膨胀卷积,并仅在相位分解明显更快时替换torch.nn.functional.conv1d。
Architecture
图中的连接均对应启动入口和适配模块中的实际调用:配置文件在入口早期创建;platform_config() 在 ZLUDA 导入前执行;ZLUDA 模块通过导入副作用修改 PyTorch 全局函数;前置依赖和安装检查随后由入口显式调用。rvc.lib.zluda 的 AMD 分支并不总是替换 conv1d,还要通过启动时测量决定。
启动顺序与边界
app.py 将当前工作目录保存到 now_dir,并追加到 sys.path。配置路径固定为 assets/config.json,模板路径固定为 assets/config_template.json;当配置文件不存在时使用 shutil.copy() 从模板创建。该逻辑依赖进程启动时的当前目录,因此从错误目录启动会直接影响配置定位和后续安装检查。
配置准备之后立即调用 platform_config()。这意味着平台环境变量在加载大量 UI 和业务模块前设置,而不是在应用已经运行后再补救。入口随后导入 rvc.lib.zluda;该模块没有显式的初始化函数,GPU 检测与 monkey patch 均发生在导入时。
1# Make sure the config file exists
2import os
3import shutil
4import sys
5
6now_dir = os.getcwd()
7sys.path.append(now_dir)
8
9CONFIG_PATH = os.path.join(now_dir, "assets", "config.json")
10CONFIG_TEMPLATE_PATH = os.path.join(now_dir, "assets", "config_template.json")
11
12if not os.path.exists(CONFIG_PATH):
13 print("Config file not found. Creating fresh from template.")
14 shutil.copy(CONFIG_TEMPLATE_PATH, CONFIG_PATH)
15
16from rvc.lib.platform import platform_config
17
18platform_config()Source: app.py
这一段的关键约束是“工作目录优先”:源码没有在这里做路径规范化,也没有捕获模板复制失败。因此模板缺失、当前目录不可写或工作目录错误时,后续启动链路可能无法正常继续;这些具体异常映射未在当前实现中单独封装。
平台适配实现
macOS ARM64
当 sys.platform == "darwin" 且 platform.machine() == "arm64" 时,platform_config() 设置:
| 环境变量 | 值 | 作用(按源码可确认范围) |
|---|---|---|
OMP_NUM_THREADS | "1" | 将 OpenMP 线程数限制为 1。 |
KMP_DUPLICATE_LIB_OK | "TRUE" | 允许重复 KMP 运行库共存。 |
源码没有在此函数中提供恢复旧值、日志输出或可配置覆盖,因此该设置是进程级环境修改,并在后续导入的模块中生效。
Windows 与 ASIO
Windows 分支读取当前工作目录下的 assets/config.json。只有当 config.get("realtime", {}).get("asio_enabled", False) 为真时,才设置 SD_ENABLE_ASIO="1"。读取失败、JSON 无法解析或文件不存在时,函数捕获通用 Exception 并静默返回;因此平台配置失败不会在这里阻断启动,也不会向调用方返回状态。
1def platform_config():
2 if sys.platform == "darwin" and platform.machine() == "arm64":
3 os.environ["OMP_NUM_THREADS"] = "1"
4 os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE"
5
6 if sys.platform == "win32":
7 try:
8 config_path = os.path.join(os.getcwd(), "assets", "config.json")
9 with open(config_path, "r", encoding="utf-8") as f:
10 config = json.load(f)
11 if config.get("realtime", {}).get("asio_enabled", False):
12 os.environ["SD_ENABLE_ASIO"] = "1"
13 except Exception:
14 passSource: platform.py
Windows 安装路径检查
check_installation() 主要针对 Windows 路径约束。函数先比较当前目录所在盘符与 SystemDrive;若不同则构造 InstallationError,要求将 Applio 移动到系统盘。该比较被一个裸 except 包围,所以在非 Windows 环境或盘符信息不可用时,异常会被忽略,并不会执行后面的 Windows 路径规则。
当盘符检查没有触发异常时,函数依次拒绝:
- 路径包含
OneDrive; - 路径包含空格;
- 路径无法用 ASCII 编码,即包含非 ASCII 字符。
这些条件通过抛出 InstallationError 实现。源码没有在该函数内部记录日志或转换为布尔值;调用方 app.py 也直接调用它,因此错误会成为启动阶段的异常,而不是 UI 内的可恢复提示。
1def check_installation():
2 try:
3 system_drive = os.getenv("SystemDrive")
4 current_drive = os.path.splitdrive(now_dir)[0]
5 if current_drive.upper() != system_drive.upper():
6 raise InstallationError(
7 f"Installation Error: The current working directory is on drive {current_drive}, but the default system drive is {system_drive}. Please move Applio to the {system_drive} drive."
8 )
9 except:
10 pass
11 else:
12 if "OneDrive" in now_dir:
13 raise InstallationError(
14 "Installation Error: The current working directory is located in OneDrive. Please move Applio to a different folder."
15 )
16 elif " " in now_dir:
17 raise InstallationError(
18 "Installation Error: The current working directory contains spaces. Please move Applio to a folder without spaces in its path."
19 )Source: installation_checker.py
核心启动流程
Sources:
启动顺序的实际含义是:前置依赖准备发生在 check_installation() 之前。若 run_prerequisites_script() 本身失败,安装路径检查可能尚未执行;本页读取到的入口代码没有为该调用增加本地重试或异常转换。依赖脚本的具体下载、缓存和错误策略不在当前源码证据范围内。
ZLUDA 与 AMD 运行时适配
ZLUDA 条件下的 STFT 替换
模块导入时首先检查 torch.cuda.is_available(),并要求 torch.cuda.get_device_name().endswith("[ZLUDA]")。只有同时满足时,才定义并安装 ZLUDA 专用 STFT、z_stft 和 z_jit。
STFT 对每个 n_fft 缓存 Fourier basis。首次计算时,它在 CPU 上对单位矩阵执行 torch.fft.fft,截取正频率部分,拼接实部和虚部,再把结果移动到 CUDA 设备;之后相同 n_fft 直接复用缓存。transform() 随后执行窗口乘法、reflect padding、按 hop_length 展开帧、矩阵乘法,并将结果重新组合成复数张量。
z_stft 只对一种特定调用路径使用 GPU 实现:win_length、center 必须为 None,且 return_complex 必须为 True。其他调用回退到原始 torch.stft,但先把输入和窗口移到 CPU,再将结果移回原设备。这种窄条件替换降低了对其他 STFT 调用行为的影响。
同时,torch.jit.script 被替换为返回一个空 torch._C.Graph() 的 z_jit,并禁用 cuDNN 与 flash/memory-efficient SDP,只启用 math SDP。以上都是模块级全局改写,因而导入 rvc.lib.zluda 的时机非常重要。
1if torch.cuda.is_available() and torch.cuda.get_device_name().endswith("[ZLUDA]"):
2 class STFT:
3 def __init__(self):
4 self.device = "cuda"
5 self.fourier_bases = {}
6
7 def _get_fourier_basis(self, n_fft):
8 if n_fft in self.fourier_bases:
9 return self.fourier_bases[n_fft]
10 fourier_basis = torch.fft.fft(torch.eye(n_fft, device="cpu")).to(
11 self.device
12 )
13 cutoff = n_fft // 2 + 1
14 fourier_basis = torch.cat(
15 [fourier_basis.real[:cutoff], fourier_basis.imag[:cutoff]], dim=0
16 )
17 self.fourier_bases[n_fft] = fourier_basis
18 return fourier_basisSource: zluda.py
AMD 膨胀卷积优化
第二条路径只要求设备名包含 AMD。模块保存原始 torch.nn.functional.conv1d 为 _conv1d,定义 _conv1d_phases() 将膨胀卷积拆成多个独立相位的普通卷积:输入先按 dilation 补齐,再 reshape/permute 为批次扩展的相位,使用 dilation 为 1 的原始卷积,最后恢复通道与时间维并裁剪到原长度。
z_conv1d() 只改写满足以下条件的调用:dilation 不为 1、stride 为 1、padding 是整数、输入位于 CUDA。其他情况完全转发到原始 _conv1d,因此普通卷积、非 1 stride、非整数 padding 或 CPU 输入不会走相位路径。
是否安装这个替换由 _dilated_conv_is_slow() 决定。它创建固定形状的 Conv1d 和 CUDA 输入,分别预热并同步计时原生实现与相位实现;只有当 phases * 1.5 < native 时才认为优化有足够 margin。基准测试或补丁安装过程发生异常时,最外层 try/except 静默保留原生 kernel。
1def z_conv1d(input, weight, bias=None, stride=1, padding=0, dilation=1, groups=1):
2 first = lambda v: v[0] if isinstance(v, (tuple, list)) else v
3 d, s, p = first(dilation), first(stride), first(padding)
4 if d == 1 or s != 1 or not isinstance(p, int) or not input.is_cuda:
5 return _conv1d(input, weight, bias, stride, padding, dilation, groups)
6 return _conv1d_phases(input, weight, bias, p, d, groups)
7
8try:
9 if _dilated_conv_is_slow():
10 torch.nn.functional.conv1d = z_conv1d
11except Exception:
12 passSource: zluda.py
Source: zluda.py
上述两个设备分支不是互斥设计上的显式 elif:ZLUDA 分支结束后,AMD 分支仍会独立检查设备名。如果某个设备名称同时满足两个字符串条件,源码允许两组全局设置按顺序生效;源码没有额外的互斥保护。
配置与启动参数
配置文件与平台键
| 项目 | 类型 | 默认/回退 | 说明 |
|---|---|---|---|
assets/config.json | JSON 文件 | 缺失时从 assets/config_template.json 复制 | 由 app.py 在启动早期创建。 |
realtime.asio_enabled | 布尔值 | 代码读取回退为 False | Windows 下为真时设置 SD_ENABLE_ASIO="1";源码未在本页范围内声明模板中的实际值。 |
OMP_NUM_THREADS | 环境变量 | macOS ARM64 时设置为 "1" | platform_config() 直接写入进程环境。 |
KMP_DUPLICATE_LIB_OK | 环境变量 | macOS ARM64 时设置为 "TRUE" | 仅在 macOS ARM64 分支设置。 |
Web UI 启动参数
app.py 定义了以下入口参数。它们控制服务监听和启动方式,不改变 ZLUDA 检测条件:
| 参数 | 类型 | 默认值 | 行为 |
|---|---|---|---|
--port | int | 6969 | 设置服务器端口。 |
--server-name | str | 127.0.0.1 | 设置服务器主机名。 |
--share | flag | 未启用 | 创建公共 Gradio share link。 |
--open | flag | 未启用 | 自动打开浏览器。 |
--client | flag | 未启用 | 启用 client mode 并挂载 realtime API。 |
1DEFAULT_SERVER_NAME = "127.0.0.1"
2DEFAULT_PORT = 6969
3MAX_PORT_ATTEMPTS = 10
4
5_ARG_PARSER.add_argument(
6 "--port", type=int, default=DEFAULT_PORT, help="Server port (default: %(default)s)"
7)
8_ARG_PARSER.add_argument(
9 "--server-name",
10 type=str,
11 default=DEFAULT_SERVER_NAME,
12 help="Server hostname (default: %(default)s)",
13)
14_ARG_PARSER.add_argument(
15 "--share", action="store_true", help="Create a public Gradio share link"
16)Source: app.py
入口还以固定参数调用 run_prerequisites_script():pretraineds_hifigan=True、models=True、exe=True。这表示启动阶段会请求三类前置准备,但当前读取的源码没有展示该函数内部的下载、缓存和重试细节,因此不能据此推导网络失败时的具体行为。
API Reference
platform_config() -> None
定义于 rvc.lib.platform。根据 sys.platform 和机器架构设置进程环境变量。macOS ARM64 分支设置 OpenMP/KMP 变量;Windows 分支读取 assets/config.json 并根据 realtime.asio_enabled 设置 ASIO 变量。
- 参数:无。
- 返回值:源码没有显式
return,因此返回None。 - 异常:Windows 配置读取块捕获所有
Exception并静默忽略;macOS 分支本身没有异常处理。
check_installation() -> None
定义于 assets.installation_checker。检查启动目录是否符合当前实现要求。
- 参数:无;检查的目录在模块导入时由
os.getcwd()保存到now_dir。 - 返回值:通过时隐式返回
None。 - 异常:路径规则不满足时抛出
InstallationError;盘符比较代码位于裸except保护范围内,相关异常会被忽略。
InstallationError(message="InstallationError")
自定义异常保存传入的 message,并把它交给基类 Exception。当前实现没有错误码、错误类别或结构化字段,因此调用方应使用异常文本判断用户需要移动目录的原因。
1class InstallationError(Exception):
2 def __init__(self, message="InstallationError"):
3 self.message = message
4 super().__init__(self.message)Source: installation_checker.py
失败模式、边界条件与并发
已实现的失败处理
- Windows 配置读取失败:
platform_config()捕获通用Exception后忽略,应用不会因为 ASIO 配置读取失败在该函数处停止。 - 安装目录不合规:
check_installation()抛出InstallationError,覆盖系统盘不一致、OneDrive、空格和非 ASCII 字符路径。 - ZLUDA STFT 不适用:不满足特定关键字参数条件时回退到原始
torch.stft,但使用 CPU 输入/窗口计算后再移回原设备。 - AMD 基准测试失败:AMD 优化外围捕获异常并保留原生
conv1d。 - AMD 优化不够快:只有相位实现比原生实现快 1.5 倍以上才安装替换;否则保持原实现。
边界与并发注意事项
这些补丁是在模块导入时修改 torch.stft、torch.jit.script、torch.nn.functional.conv1d 及 CUDA/SDP 后端开关。它们是进程级全局状态,不是线程局部配置;源码没有提供卸载补丁或并发锁。因此应在模型工作线程/服务启动前完成导入,并避免在运行中反复重新加载模块。STFT.fourier_bases 是实例字典缓存,源码没有锁;当前实例在导入时创建为 stft,若多线程同时首次请求同一 n_fft,源码没有明确的同步保证。
这些是从实现结构可以直接确认的工程约束;源码没有提供线程模型、服务 worker 数或多进程初始化策略,因此不能进一步断言多 worker 部署下的缓存共享行为。
性能与运维说明
- ZLUDA STFT 通过按
n_fft缓存 Fourier basis 避免重复构造;首次遇到新的n_fft会产生额外构造与设备传输成本。 - ZLUDA 不支持的 STFT 调用路径显式退回 CPU,再把结果移回输入设备;这保证了兼容性,但可能增加数据传输成本。
- AMD 分支在启动阶段执行 CUDA 基准测试,并使用
torch.cuda.synchronize()包围计时,因此 GPU 启动开销是预期行为;只有明确收益时才保留改写。 conv1d相位实现仅覆盖 plain dilated case;其他参数交给原生实现,避免把优化扩散到未经验证的调用形态。- 安装检查依赖当前工作目录。运维部署时应保证从 Applio 根目录启动,并在 Windows 上避免 OneDrive、空格和非 ASCII 路径。
Extension Points
当前实现没有注册式适配器接口。若要扩展平台逻辑,应在 platform_config() 中增加明确的平台/架构分支,并保留现有配置读取失败的行为边界;若要扩展 GPU 兼容层,应遵循 zluda.py 的模式:保存原函数、缩小替换条件、对不匹配输入回退,并在安装全局 patch 前做可重复的基准测试。
尤其不应直接把所有 conv1d 调用都改写为相位实现:源码通过 dilation、stride、padding 和 input.is_cuda 过滤调用,并用性能 margin 决定是否安装 patch。这些条件是当前兼容性与性能折衷的核心。