想在本机用 YuE2 生成整首歌,顺序建议反过来:先量这台机器实际能用的显存,再决定单曲能多长。显卡标称容量和可用显存经常差一截,桌面环境、其他 Python 进程、驱动预留都会占掉一部分;把权重加载进来之后剩下的余量,才决定你一次能推理多少秒的音频。
在单卡本地跑 YuE2,建议先把 nvidia-smi 的空闲显存和框架侧统计对齐,再定单曲时长;权重精度、单次生成长度、批量大小是最先可调的三个旋钮。先用很短的片段跑通端到端推理,确认采样率、时长和保存路径符合预期,再按固定步长加长并记录显存峰值。遇到爆显存,按降精度、缩时长、减批量、分段生成的顺序退;分段会让歌曲边界不连续,是取舍不是无损方案。
用 nvidia-smi 和框架显存统计确认当前基线
先在不加载模型的情况下看一次,确认机器上没有别的进程占着显存:
nvidia-smi
# 只看关键字段
nvidia-smi `--query-gpu`=name,memory.total,memory.used,memory.free,utilization.gpu `--format`=csv
# 需要持续观察时
watch -n 1 nvidia-smi
要读的是 memory.free(当前空闲显存)和下方进程表里每个 PID 的占用,而不是 memory.total。标称的 24GB、16GB 是硬件上限,真正能分配给你的通常更少;如果空闲显存已经明显小于标称,先关掉占卡的程序再往下走。
框架侧也做一次对账,在 Python 里加载权重前后各打一次:
import torch
torch.cuda.empty_cache()
print('free/total bytes:', torch.cuda.mem_get_info())
print('allocated GiB:', torch.cuda.memory_allocated()/1024**3)
print('reserved GiB:', torch.cuda.memory_reserved()/1024**3)
memory_allocated 和 memory_reserved 往往大于 nvidia-smi 里那一行增量,两者对不上时以 nvidia-smi 的空闲显存为准,因为缓存池里保留的块对别的进程仍然不可用。运行中也可以用 torch.cuda.max_memory_allocated() 看单次推理的峰值。
把权重、精度和单次生成长度与显存对上
YuE2 的显存占用大致分两块:权重常驻,以及推理过程中产生的中间张量。权重常驻由模型参数量和数值精度决定,中间张量随单次生成长度、批量大小增长。先把可调项列出来,再填配置骨架。
- 数值精度:fp32 最占显存,bf16/fp16 的占用通常低于 fp32;能否用取决于显卡是否支持,以及生成质量你能接受到什么程度。
- 单次生成长度:以秒或帧为单位,最直接影响显存峰值,也是决定一首歌能多长的参数。
- 批量大小:一次生成几条。批量变大时显存增长通常不是严格线性的,但方向明确,本地跑单曲一般就是 1。
- 采样步数与调度器:主要影响耗时,对显存峰值的影响相对小,但会拉长中间张量的存活时间。
- 注意力实现与 offload:有的实现支持更省显存的注意力后端,或把部分层放到 CPU;能不能用要看本地代码和硬件。
配置骨架(先填保守值,字段确定后再逐步放开):
model:
weight_path: '<本地权重目录>'
dtype: 'bf16' # 可选 fp32 / fp16 / bf16,按硬件支持选
device: 'cuda:0'
infer:
duration_sec: ___ # 这一项决定单曲时长上限
batch_size: 1
steps: ___
sample_rate: ___ # 以模型或仓库默认值为准
output_dir: '<输出目录>'
具体用哪个入口脚本、参数名怎么拼,以你本地仓库为准;骨架只表达哪些字段需要你填、哪些字段决定显存。填完之后,先用保守值加载一次权重,看 nvidia-smi 里 used 的增量,那部分基本就是权重常驻开销,剩下的余量才是留给单次生成长度的预算。
先用很短的一段跑通端到端推理
在拉长时长之前,先把链路跑通:权重能加载、推理不报错、文件能落盘。命令骨架如下,脚本名和参数名按你本地仓库替换:
python -m yue2.infer \
`--model` '<权重目录>' \
`--prompt` '<简短描述>' \
`--duration` 8 \
`--dtype` bf16 \
`--output` ./out/smoke.wav
执行时另开一个终端跑 watch -n 1 nvidia-smi,观察 used 在推理阶段是否稳定、有没有顶到上限。日志里要确认三行信息:采样率、实际生成时长、保存路径。同时确认 device 和 dtype 是你要的那组,避免它回落到 CPU 或 fp32。
- 采样率:和配置或模型默认值是否一致。
- 实际时长:是不是等于请求的时长,有没有被静默截断。
- 保存路径:文件是否真实存在、能播放、不是空文件。
这一步过不了,加长时长只会把问题放大。跑通后再进入加长测试。
逐步拉长单曲时长并记录显存峰值
每次只动一个变量:把 duration_sec 按固定步长往上加,批量保持 1,精度不动。步长可以先取小一点,具体取多少取决于你第一次跑通时占用了多少显存。每一轮记录以下几项:
- 请求时长与输出文件的实际时长
- 显存峰值:nvidia-smi 观察到的最高 used,或 torch.cuda.max_memory_allocated()
- 单轮耗时
- 输出是否被截断、是否出现明显静音或爆音
- 是否发生 OOM,或进程被系统杀掉
在每次推理前重置峰值统计,避免上一轮的数据串进来:
torch.cuda.reset_peak_memory_stats()
# ... 执行一次推理 ...
print('peak allocated GiB:', torch.cuda.max_memory_allocated()/1024**3)
当某一轮开始 OOM,或输出时长明显短于请求值,就退回上一个稳定时长,把它当作本机的上限。没 OOM 也不等于没问题:显存接近打满时,缓存回收和内存换页会拖慢速度,换成更长的曲子时可能直接失败。
爆显存或中途中断时的降级顺序
按下面的顺序退,每次只改一项,改完重跑同一段内容和同一时长,才能判断是哪一项起了作用:
- 降精度:fp32 换成 bf16 或 fp16。代价是细节表现可能变化,先听同一段 prompt 的输出再决定。
- 缩短单次时长:最直接的一步,但会改变歌曲结构,段落衔接可能不完整。
- 减小批量:本地跑单曲时批量一般已是 1;如果为了对比开了多条并发生成,先降回 1。
- 改分段生成:把长曲拆成多段分别生成再拼接。换来的是能跑通,代价是段与段之间的调性、节奏和响度需要额外处理,不是无损方案。
如果以上都试过仍不稳定,需要回头确认是否有其他进程在抢显存,或者权重加载方式本身产生了额外副本(例如同一份权重被加载了两次)。这两类问题靠调时长参数解决不了,先清理掉再谈单曲时长上限。