NeMo Speech ASR 微调实战(Stage 4):训练超参配置、多卡启动排障、检查点平均与独立 WER 评估
【免费下载链接】SpeechA scalable generative AI framework built for researchers and developers working on Large Language Models, Multimodal, and Speech AI (Automatic Speech Recognition and Text-to-Speech)项目地址: https://gitcode.com/GitHub_Trending/nem/Speech
本文聚焦 NeMo Speech 仓库中 ASR 微调工作流的最后一站:基于references/training-evaluation.md(.claude/skills/nemo-speech-asr-finetune技能的第 4 阶段)系统讲解优化器与训练器配置、多 GPU 启动排障、检查点平均(checkpoint averaging)以及独立评估的正确姿势。读完后,你将能够独立完成一次可复现的 ASR 微调:从trainer.max_steps+ 余弦学习率调度,到torchrun多卡容器化启动,再到用同一套独立评估命令对基线模型、最佳检查点与平均模型做公平的 WER 对比。
该文档是 SKILL.md 定义的五阶段微调工作流中的第 4 阶段(前序阶段涵盖容器与检查点选择、Lhotse 数据准备、架构检测与分词器调整),其总体姿态在 SKILL.md 中明确:优先使用 NeMo 容器、训练与验证数据加载器优先 Lhotse、用trainer.max_steps而非trainer.max_epochs控制训练长度、用val_wer作为检查点监控指标、最终质量以独立评估(而非训练日志)为准。
优化器与训练器配置
以 max_steps 为中心的训练配置
核心原则是用trainer.max_steps控制训练长度,而不是trainer.max_epochs,并让余弦(Cosine Annealing)学习率调度器使用与训练器完全相同的max_steps,保证调度曲线与训练终止点严格对齐。
初始学习率按数据规模与风险分级选择:
- 大规模或混合数据的微调:从
model.optim.lr=1e-4起步,再按需调整; - 小规模领域适配(尤其是目标领域高价值音频低于约 20 小时):从
3e-5附近起步,并密切观察早期验证曲线——SKILL.md 的"不可妥协的陷阱"部分也特别强调:小数据领域适配不要盲目使用1e-4; - 后期精调阶段或已出现不稳定/发散的 run:降到
1e-5或更低。
预热步数(warmup)设为trainer.max_steps的 1%~2%。典型的 Hydra 覆盖片段如下:
+init_from_pretrained_model=<hf-or-ngc-name> \ trainer.max_steps=50000 \ +trainer.limit_train_batches=1000 \ trainer.val_check_interval=1000 \ model.optim.lr=1e-4 \ model.optim.sched.name=CosineAnnealing \ ++model.optim.sched.max_steps=50000 \ model.optim.sched.warmup_steps=500 \ model.optim.sched.min_lr=5e-6理解 Hydra 的+与++前缀
微调入口 speech_to_text_finetune.py 支持init_from_pretrained_model(源码中以cfg.get('init_from_pretrained_model', None)读取,并与init_from_nemo_model互斥),但部分微调 YAML 并未声明该键。文档给出的规则是:
- 当 Hydra 提示键不在 struct 中时,用
+init_from_pretrained_model=...添加该键; - 对于脚本本身支持、但所选 YAML 中不存在的 trainer/model 键,一律使用
+(新增键)或++(覆盖默认值)前缀; - 不要因为"别的配置里存在类似键"就擅自去掉加号——每个 YAML 的 struct 声明是独立的。
精度选择与验证监控
- 优先考虑
trainer.precision=bf16-true:显存占用更低、可承载更大 batch,对超过 1000 小时的数据集尤其有意义。若出现发散或稳定性问题,回退到更稳定的精度模式。在两种 bfloat16 模式之间,首选bf16-true而非bf16-mixed。 - 每一次验证 run 与检查点选择都以
val_wer为准,推荐的检查点回调配置为:
exp_manager.checkpoint_callback_params.monitor=val_wer \ exp_manager.checkpoint_callback_params.mode=min \ exp_manager.checkpoint_callback_params.save_top_k=5 \ exp_manager.checkpoint_callback_params.always_save_nemo=true注意save_top_k的取值在这里具有下游含义——它决定了后面检查点平均时会平均多少个.ckpt(见下文)。
多 GPU 启动与排障
常规与 torchrun 两种启动方式
单机例行任务先走标准 Lightning 启动(trainer.devices=<num_gpus>)。当容器内任务在 NCCL 注册之后立即挂起,或某个 rank 在 DDP 接管之前把模型恢复到错误的 GPU 上时,改用torchrun --nproc_per_node=<num_gpus>直接驱动常规脚本入口。使用 torchrun 时的配套要求:
- 保留
trainer.devices=<num_gpus>; - Lhotse 数据加载器下设置
trainer.use_distributed_sampler=false(torchrun 已负责按进程分片); trainer.num_nodes只保留给真正的多机任务;单机容器微调应避免"每进程仅可见一张 GPU"的手工启动模式,除非用户明确要求调试该环境。
仓库技能给出的容器torchrun模板:
docker run --rm --gpus all --ipc=host \ --ulimit memlock=-1 --ulimit stack=67108864 \ -e PYTHONPATH=/workspace/NeMo \ -v /path/to/NeMo:/workspace/NeMo \ -v /data:/data \ nemo-speech:<tag> bash -lc ' cd /workspace/NeMo && torchrun --nproc_per_node=2 examples/asr/speech_to_text_finetune.py \ +init_from_pretrained_model=nvidia/parakeet-tdt-0.6b-v3 \ model.train_ds.manifest_filepath=/data/train.json \ model.validation_ds.manifest_filepath=/data/val.json \ ++model.train_ds.use_lhotse=true \ ++model.validation_ds.use_lhotse=true \ +trainer.use_distributed_sampler=false \ trainer.devices=2 \ trainer.max_steps=10000 '通信层与归一化层的保守设置
若任务确认卡在通信初始化阶段(NCCL P2P 或 CUDA 内存分配器问题),再启用保守的分布式环境变量:
export NCCL_CUMEM_ENABLE=0 export NCCL_P2P_DISABLE=1另外一条容易忽视的约束:trainer.sync_batchnorm保持与原始模型配置一致,除非用户明确要求修改——改变归一化同步方式可能直接破坏微调的数值行为。
检查点平均(Checkpoint Averaging)
工具行为:源码级说明
平均工具位于 checkpoint_averaging.py,从源码可以确认其工作机制:
- 它接收一个
.nemo文件(或包含唯一.nemo的目录),从.nemo的同级目录中收集所有非-last.ckpt的.ckpt文件(见 L110-L115); - 对每个 checkpoint 的
state_dict做张量累加,最后除以数量取平均;整型张量(如BatchNorm.num_batches_tracked)不做平均只做累加(见 L143-L149); - 将平均后的权重以
strict=True载入模型并另存为*-averaged.nemo(见 L151-L155)。
由此得到两个实操结论:平均的样本数 N 由上一节训练配置中的save_top_k控制;且-last.ckpt会被自动排除。
python scripts/checkpoint_averaging/checkpoint_averaging.py \ /exp/asr-ft/checkpoints/best.nemo加载失败与 PyTorch 2.6+ 兼容
- 若模型类加载失败,使用脚本的
--class_path或--import_fname_list选项(后者用于from FILE import *注入缺失类,应对AttributeError: Can't get attribute '???'错误——脚本头部 docstring 与 L69-L73 均有说明)。 - 该脚本在源码中自报弃用状态(L43 打印 "This script is deprecated..."),文档因此要求:依赖它之前先确认当前 checkout 中仍可用。
- PyTorch 2.6+ 上,
torch.load默认weights_only=True,弃用的平均工具可能因此失败。仅对可信检查点,设置TORCH_FORCE_NO_WEIGHTS_ONLY_LOAD=1后重跑:
TORCH_FORCE_NO_WEIGHTS_ONLY_LOAD=1 python scripts/checkpoint_averaging/checkpoint_averaging.py \ /exp/asr-ft/checkpoints/best.nemo这一环境变量与仓库 install 文档 的说明一致:只对可信文件使用完整的 pickle 加载能力,避免任意代码执行风险。
"最新 .nemo 不等于最佳检查点"
always_save_nemo=true会在后续保存中覆盖.nemo产物,因此不能假定目录下最新的.nemo就是验证最优。正确流程是:核对.ckpt文件名与验证日志 → 对"导出的.nemo、最佳验证检查点导出的产物、平均模型"三者分别评估 → 再决定保留哪一个。
决策规则:对最终导出的.nemo、最佳单个验证检查点产物、平均产物使用同一条独立评估命令与同一打分契约,保留独立默认 WER 最优者;若平均模型更差,记录下来并丢弃平均产物。训练中的val_wer只用于检查点选择,不是最终汇报数字——独立 WER 才是公平比较,因为它固定了解码选项、文本处理、精度与打分口径。
独立评估:命令、精度与打分契约
精度口径:评估不用 AMP
推理/评估不要使用 AMP,统一使用compute_dtype=bfloat16与amp=false。这一约束在源码中是硬性的:transcribe_speech.py 中,当compute_dtype不是float32且amp=true时直接抛出ValueError(L278-L279),即两者互斥。对每一个参与比较的模型变体,都汇报独立speech_to_text_eval.py的结果,而不是只报训练日志。
基本评估命令(同时给出matmul_precision=high与use_cer=False):
python examples/asr/speech_to_text_eval.py \ model_path=/exp/asr-ft/checkpoints/best.nemo \ dataset_manifest=/data/test.json \ output_filename=/exp/asr-ft/test_predictions.json \ batch_size=32 \ amp=false \ compute_dtype=bfloat16 \ matmul_precision=high \ use_cer=FalseRNNT/TDT 模型在支持时启用 CUDA graphs(解码器实现见 rnnt_greedy_decoding.py 中use_cuda_graph_decoder的默认开启与不可用时的自动降级逻辑):
python examples/asr/speech_to_text_eval.py \ model_path=/exp/asr-ft/checkpoints/best.nemo \ dataset_manifest=/data/test.json \ output_filename=/exp/asr-ft/test_predictions.json \ batch_size=32 \ amp=false \ compute_dtype=bfloat16 \ matmul_precision=high \ rnnt_decoding.strategy=greedy_batch \ rnnt_decoding.greedy.use_cuda_graph_decoder=true混合(Hybrid)模型则分别用 CTC 与 RNNT 解码器各评一次,对比解码器选择:
python examples/asr/speech_to_text_eval.py \ model_path=/exp/asr-ft/checkpoints/best.nemo \ dataset_manifest=/data/test.json \ decoder_type=ctc \ output_filename=/exp/asr-ft/test_predictions_ctc.json \ amp=false \ compute_dtype=bfloat16 python examples/asr/speech_to_text_eval.py \ model_path=/exp/asr-ft/checkpoints/best.nemo \ dataset_manifest=/data/test.json \ decoder_type=rnnt \ output_filename=/exp/asr-ft/test_predictions_rnnt.json \ amp=false \ compute_dtype=bfloat16 \ rnnt_decoding.strategy=greedy_batch \ rnnt_decoding.greedy.use_cuda_graph_decoder=true其中decoder_type(ctc/rnnt)正是 EvaluationConfig 中"为 Joint RNNT/CTC 模型在两种解码器之间切换"的字段。
两步评估与文本处理契约
评估流程对应 evaluation-style-contract.md 的两步法:
- 稳定解码 + 无 AMP + bfloat16 转写,把预测落到 manifest(
pred_text字段); - score-only 打分该预测 manifest,默认去掉大小写与标点后再算 WER:
python examples/asr/speech_to_text_eval.py \ dataset_manifest=/exp/asr-ft/test_predictions.json \ only_score_manifest=True \ text_processing.do_lowercase=true \ text_processing.rm_punctuation=true \ use_cer=False从 speech_to_text_eval.py 源码可确认该机制:only_score_manifest=True时跳过音频转写直接对 manifest 打分(L129-L141);text_processing.do_lowercase/rm_punctuation会对text与pred_text两侧对称地做小写化与标点删除(L158-L167);而rm_punctuation删除的标点集合由text_processing.punctuation_marks控制(默认".,?",见 EvaluationConfig 默认值)。因此:
- 转写命令打印的 WER 是原始 WER(除非该命令也传了同样的
text_processing覆盖);score-only 命令输出的才是默认指标; - 若预测中存在标点表之外的符号,要么覆盖
text_processing.punctuation_marks,要么对text与pred_text应用同一文档化的归一化规则生成派生 manifest 再打分; - 仅在用户要求或需要诊断"转写风格不匹配"时,才单独汇报 raw WER(score-only 时去掉
do_lowercase/rm_punctuation覆盖)。
对基线与微调后模型的对比,同样使用"同一命令 + 同一打分契约":
python examples/asr/speech_to_text_eval.py \ model_path=/exp/asr-ft/checkpoints/best.nemo \ dataset_manifest=/data/val.json \ output_filename=/exp/asr-ft/val_best.json \ batch_size=32 \ amp=false \ compute_dtype=bfloat16 python examples/asr/speech_to_text_eval.py \ model_path=/exp/asr-ft/checkpoints/best-averaged.nemo \ dataset_manifest=/data/val.json \ output_filename=/exp/asr-ft/val_avg.json \ batch_size=32 \ amp=false \ compute_dtype=bfloat16契约还规定每个模型变体应汇报:默认 WER/CER、(必要时)raw WER/CER、确切的预测 manifest 与文本处理设置、参考文本是原始标签还是风格归一化标签。若 raw WER 与默认 WER 差异显著,应优先视为转写风格问题而非声学回归——在默认归一化 WER 已改善的情况下,不要仅凭 raw WER 断言模型退化。
与其他脚本的分工
- 通用微调入口:
examples/asr/speech_to_text_finetune.py;架构专属配方则路由到 CTC(examples/asr/asr_ctc/speech_to_text_ctc_bpe.py)、RNNT(examples/asr/asr_transducer/speech_to_text_rnnt_bpe.py)、Hybrid RNNT/CTC 或 TDT/CTC(examples/asr/asr_hybrid_transducer_ctc/speech_to_text_hybrid_rnnt_ctc_bpe.py)、AED/Canary(examples/asr/speech_multitask/speech_to_text_aed.py)。 - 直接离线转写用
examples/asr/transcribe_speech.py;流式或分块推理模型使用对应的流式/分块推理脚本。 - 版本敏感结论(数据加载器行为、检查点列表等)以仓库当前文档为准,如
docs/source/dataloaders.rst、docs/source/asr/asr_checkpoints.rst与 Lhotse 数据加载器。
小结:一条可执行的 Stage 4 检查清单
- 以
trainer.max_steps+ 同值余弦调度组织训练,按数据规模选 LR(大数据 1e-4、小域适配 3e-5、精调/不稳定 1e-5),warmup 取 1%~2%,优先bf16-true; - 用
val_wer(mode=min, save_top_k=N, always_save_nemo=true)驱动检查点选择; - 多卡容器任务卡死时切换
torchrun模板,Lhotse 下use_distributed_sampler=false,必要时再上NCCL_CUMEM_ENABLE=0/NCCL_P2P_DISABLE=1; - 平均工具取"同目录非 last 的 N 个 ckpt"求均值,N 由 save_top_k 决定;PyTorch 2.6+ 可信检查点用
TORCH_FORCE_NO_WEIGHTS_ONLY_LOAD=1; - 最终决策只认独立评估:
amp=false compute_dtype=bfloat16,基线/最佳检查点/平均产物三者用同一条命令、同一 score-only 打分契约比较,保留默认 WER 最优者。
【免费下载链接】SpeechA scalable generative AI framework built for researchers and developers working on Large Language Models, Multimodal, and Speech AI (Automatic Speech Recognition and Text-to-Speech)项目地址: https://gitcode.com/GitHub_Trending/nem/Speech
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考