训练与微调

使用 FunASR 训练框架,在你自己的数据上微调预训练模型。

概览

FunASR 训练框架支持:

训练入口为 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}
字段类型说明
keystr唯一语音 ID
sourcestr音频文件路径(本地路径或 URL)
source_lenint音频长度,单位为 fbank 帧(1 帧 = 10ms)
targetstr转写文本
target_lenint文本 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_lengthfbank 帧数(每帧 10ms)
text_lengthtoken 数(由 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 的关键区别:

# 冻结 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_size6000token 模式:每批总帧数。example 模式:每批样本数。
dataset_conf.sort_size1024按长度排序的缓冲区大小(提高 padding 效率)。
dataset_conf.num_workers4数据加载线程数。
dataset_conf.data_split_num1将数据分为 N 组用于大规模训练(减少内存占用)。
dataset_conf.max_token_length过滤:跳过长度超过此值的样本(单位为帧/token)。
dataset_conf.min_token_length过滤:跳过长度不足此值的样本。

训练参数

参数默认值说明
train_conf.max_epoch50总训练轮数。
train_conf.log_interval1每 N 步打印一次 loss。
train_conf.validate_interval2000每 N 步进行一次验证。
train_conf.save_checkpoint_interval2000每 N 步保存一次模型。
train_conf.keep_nbest_models20保留验证准确率最高的 N 个模型。
train_conf.avg_nbest_model10对最优的 N 个模型做平均,生成最终 checkpoint。
train_conf.resumetrue若存在上次的 checkpoint,则自动恢复训练。
train_conf.use_deepspeedfalse启用 DeepSpeed ZeRO 优化。
optim_conf.lr0.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:额外切分模型参数。最大限度节省显存,但训练速度较慢。

训练监控

日志文件

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

需要关注的关键指标:

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)

  1. 减小 dataset_conf.batch_size
  2. 添加 dataset_conf.max_token_length=2000 过滤过长的语音
  3. 启用 DeepSpeed(自动切分优化器状态)
  4. 减少 dataset_conf.num_workers

训练 loss 不下降 / 梯度出现 NaN

验证集准确率不提升

大规模数据(超过 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 恢复。