Nemotron 3.5 Transcribe 转写跑到中途没反应,音频长度和显存都可能,但这两项不用一起查。先用 GPU 采样和分段日志把「卡住」拆成三种状态:模型还在算、进程已挂、卡在加载或预处理,再看显存峰值随音频时长的变化,判断是不是长度顶到上限。
判断卡点先看两个信号:nvidia-smi 的 GPU 利用率和显存占用是否还在变,以及最后一条分段日志停在 load、preprocess 还是 infer。利用率有波动、显存稳定在峰值,通常说明还在算,属于「慢」;利用率长期为 0 且进程还在,多半卡在加载、重采样或 IO;进程消失或被系统杀掉,按显存不足处理。适用前提是能拿到进程和 GPU 采样,单机单卡最容易复现,多卡或多进程场景需要结合部署方式确认。
先确认进程还活着:连续采样 GPU 利用率与显存
转写没输出时先别重启,重启会把现场抹掉。开一个终端按秒采样,看 utilization.gpu 和 memory.used 两列:
watch -n 1 'nvidia-smi `--query-gpu`=utilization.gpu,memory.used `--format`=csv,noheader'
同时确认 worker 进程是否存在、CPU 时间是否在涨:
ps -o pid,etime,pcpu,rss,cmd -p <pid>
- GPU 利用率在 0 和较高值之间反复跳、显存占用稳定不变:模型在算,任务只是慢,别急着判死,转去测耗时基线。
- GPU 利用率长期为 0、显存占用也不动,但进程还在且 CPU 时间在涨:卡在音频解码、重采样、特征预处理或队列等待。
- 进程不在了,或
nvidia-smi里看不到该 python 进程:已经挂了,常见是被 OOM 杀掉,先查显存再查系统日志(dmesg -T | grep -i oom)。
多卡机器上要确认采样的是任务实际用到的那张卡,加 -i <gpu_id> 指定会更稳。
在代码里给加载、预处理、推理三段加时间戳
光看 GPU 只能知道「在算还是不在算」,定位不到阶段。在调用链外层套一个计时函数,只改入口,不改模型代码:
import logging, time
log = logging.getLogger('asr')
def timed(stage, fn, *args, **kwargs):
t = time.perf_counter()
log.info('stage=%s event=start', stage)
out = fn(*args, **kwargs)
log.info('stage=%s event=end cost=%.3fs', stage, time.perf_counter() - t)
return out
model = timed('load', load_model, cfg)
feats = timed('preprocess', preprocess, wav_path)
text = timed('infer', transcribe, model, feats)
跑一条长音频后,日志大致会是这样(时间戳和耗时是占位):
ts=<时间> INFO stage=load event=start
ts=<时间> INFO stage=load event=end cost=<xs>
ts=<时间> INFO stage=preprocess event=start
ts=<时间> INFO stage=preprocess event=end cost=<xs>
ts=<时间> INFO stage=infer event=start
(之后长时间没有 infer event=end)
最后一条日志停在哪,卡点就在哪。停在 infer 之前,优先怀疑音频解码和切分;停在 infer 之后没有 end,回到第 1 节的采样看是在算还是显存已经满了。日志建议带 pid 和音频时长字段,多任务并发时能分开。
把音频长度和显存峰值对上
准备一张记录表,每跑一条音频填一行,字段至少包括:
| 音频时长 | 峰值显存 | 是否成功 |
|---|---|---|
| 10s | <实测> | 是 |
| 60s | <实测> | 是/否 |
| 300s | <实测> | 是/否 |
峰值显存可以另开一个终端后台采样后取最大值,不要把采样和转写放同一个前台窗口抢时间:
nvidia-smi `--query-gpu`=memory.used `--format`=csv,noheader -l 1 > mem.log
对比必须单变量:模型、精度、批大小、解码参数全部固定,只改音频时长。如果峰值随着时长近似线性上升、某档开始失败,说明长度顶到了显存上限;如果峰值几乎不变但耗时暴涨,问题在算力和预处理,不在显存。
显存不足时的典型报错与降载手段
先认错,常见文本形如:
torch.cuda.OutOfMemoryError: CUDA out of memory. Tried to allocate ... MiB
(GPU 0; ... GiB total capacity; ... GiB already allocated; ... MiB free)
有的封装会把它转成 RuntimeError,有的直接表现为进程消失、前端连接断开,这种情况用系统日志确认是否被 OOM killer 处理。确认是显存问题后,按代价从低到高动手:
- 批大小降到 1,并关掉并发转写,先让单条音频跑通。参数名各后端不同,可能是
batch_size、max_batch_size,也可能藏在服务配置里。 - 长音频切分为短片段,常见做法是切成 20 到 30 秒、相邻片段留少量重叠,切点尽量落在静音处,转完再拼接。注意跨片段的上下文会丢,标点、术语和句首尾可能要后处理。
- 切换计算精度,如 fp16、bf16 或 int8,参数名可能是
dtype、compute_type、precision、half,是否支持取决于具体后端和显卡,需要实测确认,不能想当然。 - 确认没有别的进程占着显存,
nvidia-smi里残留的僵尸进程要先清掉。
降低批大小、缩短切分、切换精度都是让任务跑完的手段,不是吞吐优化方案。
用短音频测一条耗时基线再线性外推
目的是把「慢」和「死」分开。取一段 10 秒左右的音频,同一配置连跑三次,记录墙钟耗时,取中位数作为基线,同时记下这段音频的显存峰值。
再用同一配置跑目标长音频,记录实际耗时。按 长音频时长 / 基线时长 × 基线耗时 估一个期望值:实测接近估算,说明只是线性变慢,调大超时时间或改异步任务即可;实测远大于估算但显存没顶,问题在长音频的预处理、重采样或内存交换,回头看第 2 节日志哪一段变长;实测直接失败或显存峰值出现跳变,按第 4 节降载处理。三次取中位数是为了避开首次加载和缓存带来的偏差,第一次的那次结果单独记,不要混进基线。