本地跑 Xiaomi-CocktailASR-1 这类多人对话 ASR,决定能不能跑完的通常不是“显存够不够”这一个数字,而是单段音频长度、分段方式和你这台机器上实际剩多少显存三者的组合。同一张卡,30 秒片段能过,3 分钟会话可能直接 OOM;反过来把分段压得很短,显存下来了,但说话人标签在接缝处更容易断裂。稳妥的做法是先在本机测基线,再逐档加长,不要先照别人的显卡型号猜。
先在 30 秒左右的片段上建立基线:记录模型加载前后的显存差和推理过程中的峰值,再用同一段音频裁成 1 分钟、3 分钟逐档试。重点看三件事——是否出现 CUDA out of memory、峰值显存出现在哪一档、说话人标签和时间戳是否在分段边界断裂。显存不足时优先切 CPU 或缩短分段长度,用耗时和人工核对换显存。所有参数以仓库实际配置为准,得到的结论只对自己这台机器有效。
记录一次最短音频推理的显存峰值与耗时
这一步只为一件事:拿到“至少多少显存、至少多少秒”的基线。建议挑一段 20 到 30 秒、有 2 到 3 人交替说话的干净片段,太短看不出多人切换,太长则把加载开销和推理开销混在一起。开一个终端持续记录显存,另一个终端跑推理。
# 终端 A:每秒采样一次,落到日志里
nvidia-smi `--query-gpu`=timestamp,memory.used,memory.total,utilization.gpu \
`--format`=csv -l 1 > gpu_mem.log
# 终端 B:记录整个过程的耗时
/usr/bin/time -v python infer.py `--audio` clip_30s.wav 2> time.log把三个时刻的显存读数抄下来:模型加载前、加载完成后、推理进行中。加载前后之差近似等于权重占用的显存;推理中的峰值减去加载后的值,才是这段音频长度带来的增量。耗时分两段看更好用:模型加载耗时和纯推理耗时,前者只影响启动,后者才随音频长度变化。如果推理脚本本身会打印分段信息,也一并留下。
逐步加长音频并观察显存与输出质量
用同一段音频裁出不同长度,才能让对比成立。裁剪时保持采样率不变,避免格式转换引入额外变量。
ffmpeg -i meeting.wav -t 30 -c copy clip_30s.wav
ffmpeg -i meeting.wav -t 60 -c copy clip_1m.wav
ffmpeg -i meeting.wav -t 180 -c copy clip_3m.wav按 30 秒、1 分钟、3 分钟的顺序跑,每一步记录四类信息:进程是否退出并报 CUDA out of memory 之类的错误;显存峰值是否明显抬升;说话人标签在句间切换处是否跳变或丢人;时间戳是否连续、结尾有没有吞字。标签断裂常发生在音频被内部切分的位置,而不是整段音频的开头结尾,所以核对时重点看中间接缝。一旦某一档失败,不必继续往上加,先回到上一档确认可重复,再进下一节调参数。
调整分段长度或批大小做对照
同一段音频失败,原因可能来自模型本身,也可能来自调用方式。做法是每次只改一个变量,其他保持固定。字段名各仓库写法不同,常见的是 chunk size 与 batch size,命令行参数或配置文件里都可能出现,以仓库实际命名为准,下面只是结构示例。
# 参数名需按仓库实际配置替换
python infer.py `--audio` clip_3m.wav `--chunk-size` 30 `--batch-size` 1
python infer.py `--audio` clip_3m.wav `--chunk-size` 15 `--batch-size` 1
python infer.py `--audio` clip_3m.wav `--chunk-size` 15 `--batch-size` 2判断方式:缩短分段后峰值显存下降、原本失败的长度能跑完,说明瓶颈更偏向调用方式里的单次输入长度;把批大小降到 1 仍然 OOM,且更短的音频也一样失败,说明限制更靠近模型与当前显存余量。批大小通常对吞吐影响更直接,但多人对话场景里一次塞进多段音频,边界标签的合并会变复杂,记录时要单独标注质量变化,不要只看跑没跑通。
显存不足时切换 CPU 或更小分段
回退顺序建议是先缩分段,再切设备。缩分段保留 GPU,代价是耗时增加和接缝增多;切 CPU 能绕开显存限制,代价是耗时会明显变长,质量是否可用需要在同一段音频上重新核对。
# 切 CPU 的骨架,参数名按仓库实际接口替换
CUDA_VISIBLE_DEVICES="" python infer.py \
`--audio` clip_3m.wav `--device` cpu `--chunk-size` 15当 3 分钟音频在 GPU 上始终过不去,也可以自己在外面切片再拼接,并用一小段重叠减少边界吞字。下面是结构示意,不是可直接运行的代码。
# 结构示意:函数名与接口按仓库替换
for start in range(0, total, step):
seg = cut(audio, start, start + chunk + overlap)
res = asr(seg, device=device)
merge(results, res, drop_head=overlap if start > 0 else 0)重叠区的作用是给边界留上下文,拼接时要丢掉前一段已经输出的重叠部分。说话人标签跨片合并目前多数情况下仍要人工抽查,尤其是重叠区里出现说话人切换的时候。
整理音频长度、显存与结果记录表
把上面几轮的记录并成一张表,才能判断这台机器能不能支撑你要处理的会议时长。
| 硬件/驱动 | 音频条件 | 配置 | 显存峰值 | 耗时 | 是否 OOM | 标签/时间戳 |
|---|---|---|---|---|---|---|
| GPU 型号、显存、驱动与 CUDA 版本 | 时长、采样率、说话人数 | device、chunk、batch | 推理中最大值 | 加载与推理分别记 | 成功或失败 | 连续、断裂或跳变 |
| CPU 型号、内存 | 同上,尽量同一片段 | 缩小的 chunk | 不适用 | 单独记 | 成功或失败 | 同上 |
填满这张表之后再看:能稳定跑完的最长音频是多少,对应的显存峰值离上限还有多少余量,超过之后是失败还是标签先坏。余量很小的情况,建议把分段设得比成功档再保守一些,留出多人重叠说话带来的波动。