训练与微调
使用 FunASR 训练框架,在你自己的数据上微调预训练模型。
概览
FunASR 训练框架支持:
- 在自定义领域数据上微调任意预训练模型
- 基于 PyTorch DDP 的多 GPU 训练(单机/多机)
- DeepSpeed ZeRO Stage 1/2/3 大模型训练
- 按 token 数或样本数的动态批处理
- 模型平均以获得最佳性能
- 中断后断点续训
训练入口为 funasr-train-ds(即 funasr/bin/train_ds.py),通过 torchrun 启动分布式训练。
数据准备
标准格式(Paraformer、SenseVoice)
训练数据使用 JSONL 格式——每行一个 JSON 对象:
{"key": "utt001", "source": "/path/to/audio.wav", "source_len": 90, "target": "这是转写文本", "target_len": 6}
{"key": "utt002", "source": "/path/to/audio2.wav", "source_len": 150, "target": "hello world", "target_len": 2}
| 字段 | 类型 | 说明 |
|---|---|---|
key | str | 唯一语音 ID |
source | str | 音频文件路径(本地路径或 URL) |
source_len | int | 音频长度,单位为 fbank 帧(1 帧 = 10ms) |
target | str | 转写文本 |
target_len | int | 文本 token 数 |
从 wav.scp + text.txt 生成
如果你有 Kaldi 格式的数据文件,可以用以下方式转换:
# train_wav.scp(制表符分隔:id \t 路径) utt001 /data/audio/001.wav utt002 /data/audio/002.wav # train_text.txt(制表符分隔:id \t 文本) utt001 这是转写文本 utt002 hello world
# 转换为 jsonl scp2jsonl \ ++scp_file_list='["/data/list/train_wav.scp", "/data/list/train_text.txt"]' \ ++data_type_list='["source", "target"]' \ ++jsonl_file_out="/data/list/train.jsonl"
ChatML 格式(Fun-ASR-Nano)
Fun-ASR-Nano 使用 ChatML 对话格式:
{"messages": [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "语音转写:<|startofspeech|>!/path/to/audio.wav<|endofspeech|>"},
{"role": "assistant", "content": "几点了?"}
], "speech_length": 145, "text_length": 3}
| 字段 | 说明 |
|---|---|
messages[0] | 系统提示词(固定为 "You are a helpful assistant.") |
messages[1] | 用户输入:提示词 + 音频路径(用 <|startofspeech|>!...<|endofspeech|> 包裹) |
messages[2] | 助手回复:转写文本 |
speech_length | fbank 帧数(每帧 10ms) |
text_length | token 数(由 Qwen3-0.6B 分词器计算) |
提示词变体:
• 中文转写:
• 英文转写:
• 跨语言转写:
• 不做文本规整:
• 中文转写:
语音转写:• 英文转写:
Speech transcription:• 跨语言转写:
语音转写成英文:• 不做文本规整:
语音转写,不进行文本规整:从 wav.scp + text.txt 转换:
python tools/scp2jsonl.py \ ++scp_file=data/train_wav.scp \ ++transcript_file=data/train_text.txt \ ++jsonl_file=data/train_example.jsonl
微调 Paraformer
cd examples/industrial_data_pretraining/paraformer bash finetune.sh
或者自定义关键参数:
export CUDA_VISIBLE_DEVICES="0,1" gpu_num=2 torchrun --nproc_per_node $gpu_num \ funasr/bin/train_ds.py \ ++model="iic/speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-pytorch" \ ++train_data_set_list="data/train.jsonl" \ ++valid_data_set_list="data/val.jsonl" \ ++dataset_conf.batch_size=6000 \ ++dataset_conf.batch_type="token" \ ++dataset_conf.num_workers=4 \ ++train_conf.max_epoch=50 \ ++train_conf.validate_interval=2000 \ ++train_conf.save_checkpoint_interval=2000 \ ++train_conf.keep_nbest_models=20 \ ++train_conf.avg_nbest_model=10 \ ++optim_conf.lr=0.0002 \ ++output_dir="./outputs"
微调 SenseVoice
cd examples/industrial_data_pretraining/sense_voice bash finetune.sh
数据格式与 Paraformer 相同(source/target JSONL)。区别在于 SenseVoice 内部使用自己的 dataset 类。
微调 Fun-ASR-Nano
cd examples/industrial_data_pretraining/fun_asr_nano bash finetune.sh
与 Paraformer 的关键区别:
- 使用 ChatML 数据格式(见上文)
- 需要设置
++trust_remote_code=true - 支持选择性冻结:冻结 encoder/adaptor,只训练 LLM decoder
# 冻结 encoder + adaptor,只训练 LLM(推荐用于领域适配) ++audio_encoder_conf.freeze=true ++audio_adaptor_conf.freeze=true ++llm_conf.freeze=false # 全参数微调 ++audio_encoder_conf.freeze=false ++audio_adaptor_conf.freeze=false ++llm_conf.freeze=false
推荐策略:先只训练 LLM(速度快、所需数据量少)。如果效果不够,再解冻 adaptor。只有在数据量非常大(>1000 小时)的情况下才解冻 encoder。
参数参考
数据集参数
| 参数 | 默认值 | 说明 |
|---|---|---|
dataset_conf.batch_type | "token" | "token":按总 token 数动态组批。"example":按固定样本数组批。 |
dataset_conf.batch_size | 6000 | token 模式:每批总帧数。example 模式:每批样本数。 |
dataset_conf.sort_size | 1024 | 按长度排序的缓冲区大小(提高 padding 效率)。 |
dataset_conf.num_workers | 4 | 数据加载线程数。 |
dataset_conf.data_split_num | 1 | 将数据分为 N 组用于大规模训练(减少内存占用)。 |
dataset_conf.max_token_length | — | 过滤:跳过长度超过此值的样本(单位为帧/token)。 |
dataset_conf.min_token_length | — | 过滤:跳过长度不足此值的样本。 |
训练参数
| 参数 | 默认值 | 说明 |
|---|---|---|
train_conf.max_epoch | 50 | 总训练轮数。 |
train_conf.log_interval | 1 | 每 N 步打印一次 loss。 |
train_conf.validate_interval | 2000 | 每 N 步进行一次验证。 |
train_conf.save_checkpoint_interval | 2000 | 每 N 步保存一次模型。 |
train_conf.keep_nbest_models | 20 | 保留验证准确率最高的 N 个模型。 |
train_conf.avg_nbest_model | 10 | 对最优的 N 个模型做平均,生成最终 checkpoint。 |
train_conf.resume | true | 若存在上次的 checkpoint,则自动恢复训练。 |
train_conf.use_deepspeed | false | 启用 DeepSpeed ZeRO 优化。 |
optim_conf.lr | 0.0002 | 学习率。 |
多 GPU 训练
单机多卡
export CUDA_VISIBLE_DEVICES="0,1,2,3"
gpu_num=4
torchrun --nnodes 1 --nproc_per_node $gpu_num \
funasr/bin/train_ds.py ${train_args}
多机多卡
# 机器 1(主节点,IP=192.168.1.1)
torchrun --nnodes 2 --node_rank 0 --nproc_per_node 4 \
--master_addr=192.168.1.1 --master_port=12345 \
funasr/bin/train_ds.py ${train_args}
# 机器 2
torchrun --nnodes 2 --node_rank 1 --nproc_per_node 4 \
--master_addr=192.168.1.1 --master_port=12345 \
funasr/bin/train_ds.py ${train_args}
DeepSpeed
对于大模型(如 Fun-ASR-Nano 8 亿参数),启用 DeepSpeed ZeRO:
++train_conf.use_deepspeed=true ++train_conf.deepspeed_config=./deepspeed_conf/ds_stage1.json
Stage 1 配置(推荐的起点):
{
"train_micro_batch_size_per_gpu": 1,
"gradient_accumulation_steps": 1,
"bf16": {"enabled": true},
"zero_optimization": {
"stage": 1,
"reduce_bucket_size": 5e8,
"allgather_bucket_size": 5e8
}
}
各 Stage 的适用场景:
• Stage 1:切分优化器状态。适用于大多数情况。
• Stage 2:额外切分梯度。适用于更大的模型。
• Stage 3:额外切分模型参数。最大限度节省显存,但训练速度较慢。
• Stage 1:切分优化器状态。适用于大多数情况。
• Stage 2:额外切分梯度。适用于更大的模型。
• Stage 3:额外切分模型参数。最大限度节省显存,但训练速度较慢。
训练监控
日志文件
tail -f outputs/log.txt # 输出示例: # train, rank: 0, epoch: 0/50, step: 6990, (loss_avg_rank: 0.327), # (acc_avg_epoch: 0.795), (lr: 1.165e-04), # GPU memory: usage: 3.8GB, peak: 18.3GB
需要关注的关键指标:
loss_avg_epoch:应随训练持续下降acc_avg_epoch:应持续上升(最重要的指标)lr:当前步的学习率GPU memory:峰值不应超过你的 GPU 显存容量
TensorBoard
tensorboard --logdir outputs/log/tensorboard # 打开 http://localhost:6006
使用微调后的模型
如果 outputs/ 目录中有 configuration.json
from funasr import AutoModel model = AutoModel(model="./outputs") res = model.generate(input="test.wav") print(res[0]["text"])
如果没有 configuration.json
funasr ++model="./outputs" \ ++config-path="./outputs" \ ++config-name="config.yaml" \ ++init_param="./outputs/model.pt" \ ++input="test.wav"
技巧与常见问题
训练时显存溢出(OOM)
- 减小
dataset_conf.batch_size - 添加
dataset_conf.max_token_length=2000过滤过长的语音 - 启用 DeepSpeed(自动切分优化器状态)
- 减少
dataset_conf.num_workers
训练 loss 不下降 / 梯度出现 NaN
- 降低学习率(建议尝试 0.00005)
- 检查数据质量——损坏的音频文件会导致 NaN
- 对于 Fun-ASR-Nano:先冻结 encoder 开始训练
验证集准确率不提升
- 增加训练数据(微调至少需要约 10 小时数据)
- 检查领域匹配度——模型在差异极大的领域上可能效果不佳
- 尝试逐步解冻更多层
大规模数据(超过 10,000 小时)
使用数据分片避免内存问题:
# 将数据分为多个片段,每次加载 2 个 ++dataset_conf.data_split_num=256 # data.list 中包含各片段的 jsonl 路径: # data/train.0.jsonl # data/train.1.jsonl # ... ++train_data_set_list="data/data.list"
崩溃后恢复训练
设置 ++train_conf.resume=true(默认已开启)。训练会自动从 output_dir 中最新的 checkpoint 恢复。