YuE2 本地跑音乐生成,先算显存再定单曲时长

文章导读
想在本机用 YuE2 生成整首歌,顺序建议反过来:先量这台机器实际能用的显存,再决定单曲能多长。显卡标称容量和可用显存经常差一截,桌面环境、其他 Python 进程、驱动预留都会占掉一部分;把权重加载进来之后剩下的余量,才决定你一次能推理多少秒的音频。
📋 目录
  1. 壹 用 nvidia-smi 和框架显存统计确认当前基线
  2. 贰 把权重、精度和单次生成长度与显存对上
  3. 叁 先用很短的一段跑通端到端推理
  4. 肆 逐步拉长单曲时长并记录显存峰值
  5. 伍 爆显存或中途中断时的降级顺序
A A

想在本机用 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 本地跑音乐生成,先算显存再定单曲时长

把权重、精度和单次生成长度与显存对上

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 的增量,那部分基本就是权重常驻开销,剩下的余量才是留给单次生成长度的预算。

先用很短的一段跑通端到端推理

在拉长时长之前,先把链路跑通:权重能加载、推理不报错、文件能落盘。命令骨架如下,脚本名和参数名按你本地仓库替换:

YuE2 本地跑音乐生成,先算显存再定单曲时长
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,精度不动。步长可以先取小一点,具体取多少取决于你第一次跑通时占用了多少显存。每一轮记录以下几项:

YuE2 本地跑音乐生成,先算显存再定单曲时长
  • 请求时长与输出文件的实际时长
  • 显存峰值: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 也不等于没问题:显存接近打满时,缓存回收和内存换页会拖慢速度,换成更长的曲子时可能直接失败。

爆显存或中途中断时的降级顺序

按下面的顺序退,每次只改一项,改完重跑同一段内容和同一时长,才能判断是哪一项起了作用:

  1. 降精度:fp32 换成 bf16 或 fp16。代价是细节表现可能变化,先听同一段 prompt 的输出再决定。
  2. 缩短单次时长:最直接的一步,但会改变歌曲结构,段落衔接可能不完整。
  3. 减小批量:本地跑单曲时批量一般已是 1;如果为了对比开了多条并发生成,先降回 1。
  4. 改分段生成:把长曲拆成多段分别生成再拼接。换来的是能跑通,代价是段与段之间的调性、节奏和响度需要额外处理,不是无损方案。

如果以上都试过仍不稳定,需要回头确认是否有其他进程在抢显存,或者权重加载方式本身产生了额外副本(例如同一份权重被加载了两次)。这两类问题靠调时长参数解决不了,先清理掉再谈单曲时长上限。