拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

CosyVoice2 LLM GRPO 强化学习微调实战:基于 veRL 的中文 TTS 全流程训练指南

CosyVoice2 LLM GRPO 强化学习微调实战:基于 veRL 的中文 TTS 全流程训练指南
  • 语音
  • 音频
  • 人工智能
  • 大模型
  • 微调
  • 预训练
  • 模型推理服务

【免费下载链接】CosyVoice

Multi-lingual large voice generation model, providing inference, training and deployment full-stack ability.

项目地址:https://gitcode.com/gh_mirrors/cos/CosyVoice
点击查看免费下载

导读

本文基于 examples/grpo/cosyvoice2/README.md 展开,完整讲解如何在 CosyVoice2 开源仓库中,借助 veRL 框架对CosyVoice2-0.5B 语音大语言模型(LLM)进行GRPO(Group Relative Policy Optimization)强化学习微调,以降低合成语音的字错误率(CER)。读者将掌握从环境搭建、数据预处理、基于 ASR 的奖励函数设计、分布式训练、分布式推理评估到模型格式回导的端到端实操能力,并理解每个阶段对应的源码实现细节。

一、背景:为什么要用强化学习微调 TTS LLM

CosyVoice2 采用 "LLM + 编解码器" 的架构:LLM 负责将文本转换为离散语音 token(形如<|s_12345|>),再由预训练 U-Net 声码器(token2wav)还原为 16 kHz 波形。在常规的 SFT(监督微调)阶段,模型学习的是"给定文本生成语音 token"的下一 token 预测任务;而GRPO 强化学习则直接以"合成语音与真实文本的匹配程度"作为优化目标,通过奖励信号引导模型输出更容易被准确识读的语音。

仓库中给出的实验证据表明(详见本文"八、实验结果"小节),GRPO 微调可将 CosyVoice3zero_shot_zh零样本中文测试集上的 CER 从官方模型的 4.08% 降低到 3.36%。这正是本 recipe 的核心价值:用与评估指标(ASR 错误率)一致的奖励函数,直接优化 TTS 合成质量。

二、整体工作流:六阶段流水线总览

整个 recipe 由一个多阶段的 shell 脚本 run.sh 驱动,通过bash run.sh <start_stage> <stop_stage>控制执行区间:

阶段作用关键产物
stage -2本地安装依赖(无 Docker 时)verl、pytriton、PytritonSenseVoice 等
stage -1下载官方 CosyVoice2-0.5B 并转为 HuggingFace 兼容格式transformers_cosyvoice2_llm
stage 0数据准备:JSONL → veRL Parquetdata/parquet_aishell3/{train,test}.parquet
stage 1启动 token2wav + ASR 奖励服务器Triton 服务(8000/8001/8002 端口)
stage 2GRPO 训练FSDP 分片权重(checkpoints 目录)
stage 3合并 FSDP 分片为 HF 格式merged_hf_model
stage 4分布式推理 + WER 评估outputs_*目录下的 wav 与识别结果
stage 5HF 格式回导为 CosyVoice 仓库格式llm-new.pt

从源码结构看,run.sh 将"训练、评估、导出"三个环节组织为一条可重复执行的流水线,并对global_step_100/200/300/400/500五个中间检查点逐一执行评估(见steps=(100 200 300 400 500)),便于观察训练过程中的 CER 变化趋势。

三、环境搭建

官方推荐直接使用预构建的 Docker 镜像,该镜像已内置 veRL 0.4、vLLM 0.8.5、Megatron-Core 0.12.2、TensorRT-LLM 2.2 等训练与推理所需组件:

docker pull soar97/verl:app-verl0.4-vllm0.8.5-mcore0.12.2-te2.2

仓库内的 Dockerfile 以此镜像为基础,额外安装 requirements.txt 中的依赖(conformer、diffusers、modelscope、onnxruntime-gpu、WeTextProcessing、s3tokenizer、tensorrt、sherpa_onnx、jiwer、pypinyin 等),并安装两个关键定制包:

  • yuekaizhang/verl(thread分支):GRPO 训练框架本体;
  • yuekaizhang/PytritonSenseVoice:为 Triton 服务器封装 SenseVoice ASR 模型。

如果 Docker 不可用,可直接执行bash run.sh -2 -2(对应脚本中stage -2),它会在 conda 环境中创建cosyvoice2(Python 3.10)、克隆并安装 verl(USE_MEGATRON=0)、安装 requirements 与nvidia-pytriton。

注意:run.sh 开头设置了export PYTHONPATH=/workspace/CosyVoice,同时sys.path.append("/workspace/CosyVoice/third_party/Matcha-TTS")在多个脚本中出现,说明训练/推理代码依赖仓库内的 Matcha-TTS 第三方实现与 CosyVoice2 模块(见 third_party/Matcha-TTS),请按此约定组织工作目录。

四、数据准备:JSONL → veRL Parquet

4.1 输入数据格式

prepare_data.py 期望输入 JSON/JSONL 文件,每条样本至少包含如下字段:

{ "text": "An example sentence to be synthesized." }

README 建议从 SparkAudio/voxbox 数据集的 metadata 目录下载 JSONL 文件(如 aishell-3.jsonl)。run.sh 的stage 0给出了实际的下载与切分方式:

wget -O data/aishell-3.jsonl https://huggingface.co/datasets/SparkAudio/voxbox/resolve/main/metadata/aishell-3.jsonl head -n 80000 data/aishell-3.jsonl > data/train.jsonl # 总计 88035 条,取前 80000 条作训练 tail -n 100 data/aishell-3.jsonl > data/test.jsonl # 取最后 100 条作测试

4.2 转换为 veRL 格式

执行:

bash run.sh 0 0

脚本内部调用prepare_data.py,核心逻辑(对应 prepare_data.py 中的make_map_fn)为每条样本构造一条 CosyVoice2 风格的对话:

data = { "data_source": f"{args.train_file}_{args.test_file}", "prompt": [ {"role": "user", "content": question}, # 待合成的文本 {"role": "assistant", "content": answer}, # 空字符串,留给 LLM 生成语音 token ], "ability": "text-to-speech", "reward_model": {"style": "rule", "ground_truth": text}, "extra_info": {"split": split, "index": idx, "text": text}, }

每个样本被自动包装成 CosyVoice2 风格的 prompt,使 LLM 学习输出 CosyVoice2 语音 token。处理完成后生成两个 Parquet 文件:

data/parquet_tiny/train.parquet data/parquet_tiny/test.parquet

(run.sh 实际输出到data/parquet_aishell3/,与 README 中示例目录parquet_tiny略有差异,以实际脚本为准。)若指定--hdfs_dir,还会通过 verl 的 hdfs_io 工具将本地 Parquet 复制到 HDFS。

五、奖励函数与 ASR 服务器

5.1 奖励计算链路

为了计算奖励,需要一个轻量级服务器完成三步工作:

  1. token → 波形:用 CosyVoice2 预训练 U-Net 模型将生成的语音 token 还原为 16 kHz 波形;
  2. ASR 转写:用 SenseVoice 将波形转写为文本;
  3. 拼音级错误率:将转写文本与真实文本(ground truth)在拼音层面计算错误率,并映射为 0~1 的奖励分数。

启动服务器(stage 1,建议在独立终端或独立 GPU 上运行):

bash run.sh 1 1 # Triton server listens on ports 8000/8001/8002

5.2 服务器源码解析

服务器实现在 token2wav_asr_server.py 中,基于Pytriton(Python 版 Triton Inference Server)。关键点如下:

  • 通过TritonConfig(http_port=8000, grpc_port=8001, metrics_port=8002)暴露三个端口,与 README 注释一致;
  • 每个 GPU 上加载一个OmniSenseVoiceSmall("iic/SenseVoiceSmall")实例和一个CosyVoice2("/workspace/CosyVoice2-0.5B", load_jit=True, load_trt=True, fp16=True)编解码器(见_Token2Wav_ASR.__init__),注意通过torch.cuda.device(self.device_id)保证两者落在同一张卡上;
  • 模型输入为TOKENS(INT32 语音 token 序列)、TOKEN_LENS、GT_TEXT(真实文本),输出为REWARDS与TRANSCRIPTS;
  • 合成时从yuekai/aishell数据集的 test 划分中随机抽取 prompt 语音与 prompt 文本(get_random_prompt_from_dataset),模拟零样本(zero-shot)说话人克隆场景。

奖励分数的具体计算(对应 token2wav_asr_server.py):

gt_pinyin = lazy_pinyin(gt_norm, style=Style.TONE3, tone_sandhi=True, neutral_tone_with_five=True) hyp_pinyin = lazy_pinyin(hyp_norm, style=Style.TONE3, tone_sandhi=True, neutral_tone_with_five=True) c = float(wer(" ".join(gt_pinyin), " ".join(hyp_pinyin))) reward_val = 1.0 - np.tanh(3.0 * c) reward_val = max(0.0, min(1.0, reward_val))

即先对中英文文本做归一化(ZhNormalizer去标点、小写),再转成带声调(TONE3)的拼音序列,用 jiwer 计算拼音级 WER,最后用1 - tanh(3*WER)平滑映射到 [0,1]:WER 越低奖励越接近 1。

5.3 训练侧的自定义奖励函数

训练端在 reward_tts.py 中实现,通过 HTTP 请求调用上述 Triton 服务:

  • compute_score(data_source, solution_str, ground_truth, ...)是 veRL 约定的自定义奖励入口,返回 [0,1] 分数;
  • _parse_ids用正则r"<\|s_(\d+)\|>"从模型生成的 token 字符串中提取语音 token ID;
  • _remote_reward将 token 数组与 ground truth 打包为 JSON,POST 到http://localhost:8000/v2/models/token2wav_asr/infer,解析返回的outputs[0].data[0]作为奖励;任何异常(KeyError/IndexError/TypeError 或网络异常)都会安全回落为0.0,保证训练不因单点失败中断。

该文件还附带 CLI 快速测试入口(--input/--max-samples/--debug/--no-interactive),可从 JSONL 中读取text与code字段,逐条验证奖励打分是否正常。

六、GRPO 训练

6.1 启动训练

bash run.sh 2 2

run.sh 的stage 2调用了verl.trainer.main_ppo,以下是实际传给训练器的完整关键参数(对应 run.sh):

export CUDA_VISIBLE_DEVICES="0,1,2,3,4,5,6,7" n_gpus_per_node=8 micro_batch_size=4 train_batch_size=32 python3 -m verl.trainer.main_ppo \ algorithm.adv_estimator=grpo \ data.train_files=data/parquet_aishell3/train.parquet \ data.val_files=data/parquet_aishell3/test.parquet \ data.train_batch_size=$train_batch_size \ data.max_prompt_length=1024 \ data.max_response_length=512 \ data.truncation='error' \ actor_rollout_ref.model.use_remove_padding=False \ actor_rollout_ref.model.path=$sft_model_path \ actor_rollout_ref.actor.optim.lr=1e-6 \ actor_rollout_ref.actor.ppo_mini_batch_size=32 \ actor_rollout_ref.actor.ppo_micro_batch_size_per_gpu=$micro_batch_size \ actor_rollout_ref.actor.use_kl_loss=False \ actor_rollout_ref.model.enable_gradient_checkpointing=True \ actor_rollout_ref.actor.fsdp_config.param_offload=False \ actor_rollout_ref.actor.fsdp_config.optimizer_offload=False \ actor_rollout_ref.rollout.log_prob_micro_batch_size_per_gpu=$micro_batch_size \ actor_rollout_ref.rollout.tensor_model_parallel_size=1 \ actor_rollout_ref.rollout.name=vllm \ actor_rollout_ref.rollout.gpu_memory_utilization=0.6 \ actor_rollout_ref.rollout.do_sample=true \ actor_rollout_ref.rollout.temperature=0.8 \ actor_rollout_ref.rollout.top_p=0.95 \ actor_rollout_ref.rollout.top_k=25 \ actor_rollout_ref.rollout.n=4 \ actor_rollout_ref.rollout.val_kwargs.do_sample=true \ actor_rollout_ref.rollout.val_kwargs.temperature=0.8 \ actor_rollout_ref.rollout.val_kwargs.top_p=0.95 \ actor_rollout_ref.rollout.val_kwargs.top_k=25 \ reward_model.reward_manager=prime \ custom_reward_function.path=reward_tts.py \ custom_reward_function.name=compute_score \ trainer.project_name='cosyvoice2_grpo' \ trainer.experiment_name=$exp_name \ trainer.logger=['console','wandb'] \ trainer.n_gpus_per_node=$n_gpus_per_node \ trainer.nnodes=1 \ trainer.save_freq=100 \ trainer.test_freq=100 \ trainer.resume_mode='auto' \ trainer.total_epochs=1 \ trainer.val_before_train=False

参数含义解读:

  • 算法与数据:algorithm.adv_estimator=grpo使用 GRPO 替代 PPO;训练/验证数据即stage 0生成的 Parquet;data.max_prompt_length=1024、data.max_response_length=512限制 prompt 与语音 token 序列长度。
  • Actor 训练侧:lr=1e-6保持较低学习率,避免破坏已学到的语音生成能力;use_kl_loss=False表示不叠加 KL 散度约束;开启梯度检查点以节省显存。
  • Rollout 采样侧:name=vllm使用 vLLM 做采样;temperature=0.8 / top_p=0.95 / top_k=25 / n=4表示每个 prompt 采样 4 条轨迹(GRPO 正是利用组内相对优势估计奖励);验证阶段(val_kwargs.*)使用相同的采样配置。
  • 奖励侧:reward_model.reward_manager=prime采用 PRIME 奖励管理,custom_reward_function.path=reward_tts.py、name=compute_score指定自定义奖励函数。
  • 训练器:trainer.n_gpus_per_node=8、nnodes=1,即 8 卡单机训练;save_freq=100每 100 步保存一次检查点;total_epochs=1完整跑 1 个 epoch;resume_mode='auto'自动断点续训。

训练前需先完成stage -1,将官方 CosyVoice2-0.5B 模型转换为 HuggingFace 兼容检查点(pretrained_to_huggingface.py)。该脚本的核心工作是:

  • 将 CosyVoice2 的llm_decoder(语音 token 输出层)权重与 bias 拷入新建的lm_head,将speech_embedding、llm_embedding拷入input_embeddings对应区间;
  • 扩展词表,新增 6561 个<|s_0|>~<|s_6560|>语音 token,以及<|eos1|>、<|eos2|>、<|eos3|>、<|sos|>、<|task_id|>等特殊 token;
  • 设置generation_config(temperature=1.0、top_p=0.8、top_k=25)与多个 eos_token_id,并以tie_word_embeddings=False、use_bias=True保存,使模型与 Transformers 的 Qwen2.5-0.5B 结构兼容。

[!TIP]lm_head bias 说明:README 特别提示训练期间会禁用 lm_head 的 bias,这是为了与 VLLM 和 Transformers 的 Qwen 模型保持兼容。run.sh 的stage -1注释也明确指出:"we remove the lm_head's bias to make it compatible with the Qwen2.5-0.5B model in Transformers"。

七、评估与模型导出

7.1 合并权重(stage 3)

训练结束后,检查点以 FSDP 分片形式存储。执行以下命令合并权重并导出 HuggingFace 风格检查点:

bash run.sh 3 3 # merges weights into $llm_path/merged_hf_model

run.sh 内部对global_step_100/200/300/400/500逐一步骤执行:

python -m verl.model_merger merge \ --backend fsdp \ --local_dir $llm_path/actor \ --target_dir $llm_path/merged_hf_model

7.2 分布式推理与 WER 评估(stage 4)

bash run.sh 4 4

该命令通过 infer_dataset.py 启动分布式推理,并用 scripts/compute_wer.sh 计算 WER。run.sh 中实际命令为:

CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 \ torchrun --nproc_per_node=8 \ infer_dataset.py \ --output-dir $output_dir \ --llm-model-name-or-path $model_path \ --token2wav-path $token2wav_path \ --split-name ${dataset}

其中dataset=zero_shot_zh(CosyVoice3 测试集)为默认,也可通过dataset=test_zh切换到 Seed-TTS 测试集。

infer_dataset.py 的实现要点:

  • 使用AutoModelForCausalLM+AutoTokenizer直接加载合并后的 HF 模型,model.generate采用max_new_tokens=2048与top_p/temperature/top_k采样生成语音 token;
  • 内置专门的 chat 模板TEMPLATE(以<|im_start|>包裹,并在 user 消息前加 "Convert the text to speech: " 前缀);
  • 通过extract_speech_ids解析<|s_xxxxx|>形式的 token,交给 CosyVoice2 的 flow + hift 解码为 24 kHz 波形,写入{output_dir}/{utt}.wav;
  • 依据 split 名自动选择数据集:含zero的 split 使用yuekai/CV3-Eval并加载s3tokenizer(speech_tokenizer_v2_25hz)编码 prompt 语音,否则使用yuekai/seed_tts_cosy2。

评估脚本 scripts/compute_wer.sh 使用 sherpa-onnx 的 Paraformer 中文模型(sherpa-onnx-paraformer-zh-2023-09-14,首次运行自动下载)对生成 wav 做贪婪解码转写,再与 ground truth 计算 WER,最终调用 scripts/offline-decode-files.py 输出每句识别结果。

7.3 导出为 CosyVoice 仓库格式(stage 5)

若要在官方 CosyVoice 仓库(cosyvoice/cli/cosyvoice.py 中的CosyVoice2接口)中直接使用 RL 训练后的模型,需要将 HF 检查点转回 CosyVoice 的llm.pt格式:

bash run.sh 5 5

run.sh 内部调用:

python3 huggingface_to_pretrained.py \ --hf-cosyvoice2-llm-path $llm_path/merged_hf_model \ --output-path /workspace/CosyVoice2-0.5B/llm-new.pt

huggingface_to_pretrained.py 的逻辑是逆向pretrained_to_huggingface.py:从model.safetensors中把lm_head的语音 token 区间切片还原为llm_decoder.weight,把embed_tokens的语音区间还原为speech_embedding.weight,并跳过lm_head.bias(因为训练时已禁用 bias),最终以tie_word_embeddings=True的方式重建llm.pt。

[!TIP] README 明确提示:转换后 RL 模型的精度相对 HuggingFace 格式略有下降,因此若要追求最佳效果,建议直接使用 HuggingFace 格式的推理代码(如infer_dataset.py),而不是转换后的 CosyVoice 原生格式。

八、实验结果

README 给出了 GRPO 微调前后在两个中文测试集上的 CER(字错误率)对比:

ModelSeed-TTStest_zhCERCosyVoice3zero_shot_zhCERComment
CosyVoice2 LLM (official)1.45%4.08%官方模型基线
CosyVoice2 LLM + GRPO1.37%3.36%HuggingFace 格式模型

可见:

  • 在 Seed-TTStest_zh上 CER 从 1.45% 微降至 1.37%;
  • 在更具挑战的 CosyVoice3 零样本中文测试集zero_shot_zh上,CER 从 4.08% 显著降至3.36%(相对下降约 17.6%)。

其中官方基线对应 CosyVoice2 论文报告的数据,GRPO 结果对应 RL 微调后的 HuggingFace 格式模型(即 stage 3 产出的merged_hf_model)。README 同时致谢了 ch-tts-llasa-rl-grpo 项目对本实现的启发。

九、复现要点与注意事项

  1. 工作目录约定:run.sh 中export PYTHONPATH=/workspace/CosyVoice,多个脚本依赖/workspace/CosyVoice2-0.5B路径存放官方模型与 token2wav,请按脚本约定组织目录(模型可用modelscope download --model iic/CosyVoice2-0.5B获取,或直接使用 HF 兼容检查点yuekai/cosyvoice2_llm)。
  2. 硬件与显存:训练默认 8 卡、gpu_memory_utilization=0.6,微批大小 4、训练批大小 32;请根据自身CUDA_VISIBLE_DEVICES与 GPU 显存调整micro_batch_size等超参。
  3. 奖励服务器先行:训练(stage 2)依赖奖励服务器(stage 1)正常运行,二者通常部署在不同 GPU 或不同终端,训练端通过http://localhost:8000访问。
  4. bias 兼容性:训练阶段禁用了lm_head.bias,转换回 CosyVoice 格式时权重会重新补零 bias,这是精度轻微下降的可能来源之一;追求最佳 CER 请使用 HF 格式推理。
  5. 测试集切换:评估时dataset=zero_shot_zh与dataset=test_zh对应不同的数据源(yuekai/CV3-Eval与yuekai/seed_tts_cosy2)与不同的 prompt 编码方式(是否加载 s3tokenizer),切换时无需改动脚本。

十、结语

本文以 examples/grpo/cosyvoice2/README.md 为主线,结合仓库内的 run.sh、reward_tts.py、token2wav_asr_server.py、prepare_data.py、infer_dataset.py 等实现,完整梳理了"数据准备 → 奖励服务 → GRPO 训练 → 权重合并 → 分布式评估 → 格式回导"的全链路。通过这套 recipe,开发者可以在 CosyVoice2 上以 ASR 错误率为导向的奖励信号做强化学习微调,在零样本中文 TTS 场景下获得可复现的 CER 收益(4.08% → 3.36%),并可直接接入 veRL 生态进行更大规模的 RL 训练探索。

  • 语音
  • 音频
  • 人工智能
  • 大模型
  • 微调
  • 预训练
  • 模型推理服务

【免费下载链接】CosyVoice

Multi-lingual large voice generation model, providing inference, training and deployment full-stack ability.

项目地址:https://gitcode.com/gh_mirrors/cos/CosyVoice
点击查看免费下载

相关推荐

上一篇:3分钟掌握sqlx:让Go数据库操作效率提升10倍的实用指南
下一篇:终极指南:Android Sunflower的ViewModel架构与Repository模式最佳实践

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

返回列表