跳到主要内容
Python AI 算法
Qwen2.5 思维链微调实战:多卡 LoRA 完整代码示例 综述由AI生成 Qwen2.5 大模型的思维链(CoT)微调实战。内容涵盖 CoT 技术原理、高质量数据集准备方法、昇腾 NPU 与 Nvidia GPU 双环境搭建指南、关键提示词模板构建注意事项、以及完整的 LoRA 微调代码与多卡训练脚本。通过实测对比,验证了 CoT 微调能显著提升模型在数学推理等任务中的逐步思考能力。此外,文章还提供了 SwanLab 可视化监控方案及 Gradio 部署 Demo,并总结了微调过程中的最佳实践与常见问题解决方案。
刀狂 发布于 2025/2/7 更新于 2026/5/31 19 浏览Qwen2.5 思维链微调实战:多卡 LoRA 完整代码示例
最近对于 Scaling Law 的讨论异常火热。包括 Ilya 大神自己都下场演讲关于大模型数据规模碰壁的问题。直觉上,现在大模型思维的过程更像是人对一件事情直觉的反应,而不是多步思考和迭代思考的过程。像 OpenAI o1 或者强化对齐可能是通往 AGI 的方法之一。刚好趁这个机会尝试一下一直没有进行的思维链微调。下面简单介绍一下思维链技术,并且使用阿里通义千问进行 CoT 数据微调并且简单测试一下。
网上关于思维链微调的实操比较少,甚至对于 Qwen 的指令微调高质量的文章都不多,许多细节都描述的不清楚,希望这篇文章能够进一步帮助到读者微调 Qwen 时能够关注到一些细节。
本篇教程专门做了 openMind Library 的适配,兼容华为昇腾 910 卡。同时也支持 Nvidia GPU 环境。
思维链技术介绍
思维链技术(Chain of Thought,也简称为 CoT),最早由 Jason Wei 等人在 Chain-of-Thought Prompting Elicits Reasoning in Large Language Models 文章提出。简单来说就是通过提示词让模型能够将一个复杂的问题分步思考。比如举个文章中提到的例子,一个数学问题是:
食堂有 23 个苹果。如果他们用掉了 20 个来做午餐,又买了 6 个,现在他们有多少个苹果?
对于一个人类,他的思考步骤是:
食堂有 23 个苹果,用了 20 个,所以是 23-20=3
又买了 6 个,所以是 3+6=9
共有 9 个苹果
当然这个思维过程还能猜得更碎。对于进行了'指令微调'的模型来说,更倾向于简短的回答,比如直接回答'他现在有 XX 个苹果',而且对于一个需要多步计算的数学题往往是错误的。CoT 技术的主要目标就是通过提示词让模型一步一步来,像上面的思考步骤那样要求模型不仅回答问题,同时还将问题的生成过程写出来。
Jason Wei 的这篇文章的工作是在提示词上做的(文中分了 few-shot 和 zero-shot 两种方式),用学术些的话来说就是'上下文学习'。这篇文章的实验部分证明了 CoT 确实能有效提升 LLM 的推理能力,尤其是数学任务。很多人会想到,能否用微调的方式直接将这种'一步步思考'的能力直接微调到模型中呢?实际上 Jason Wei 大神也很快想到了,所以在紧接着下一篇 Scaling Instruction-Finetuned Language Models、Google 的 FLAN 数据集改进版 FLAN PaLM 中直接引入了 CoT 数据集。
下图展示了 FLAN PaLM 所使用微调数据集,和对 CoT 部分的消融实验,很明显 CoT 使得模型能够具备更强的 zero-shot 能力。(建议读一下原文,CoT 部分在第四章节)。
Qwen2.5 思维链微调实操
CoT 数据集准备
本文使用的是早年收集的一个 CoT 英文数据集,从质量判断是利用 GPT4+ 提示词生成的。现在网上已经有许多高质量的 CoT 数据集,包括一些数据集专门区分了思考中间过程与返回结果,以及一些将思维步骤使用 Special Token 进行分隔的。
本文考虑到仅仅是练手项目,选用了最基础的数据集,并且使用经过指令微调的 Qwen2.5-7B 将英文数据集进一步翻译成了中文 + 整理成了标准的 Instruction-Input-Output 样式,以方便对 Qwen 模型的微调和练手学习。注意这个数据集仅仅是练手,LLM 翻译的质量并不高,很容易被要翻译的内容带偏了。下图展示了部分数据集和样式:
{
"instruction" : "这家宠物店一共有多少只啮齿动物?宠物店里有 6 个笼子装着啮齿动物。其中 3 个笼子里各有 10 只仓鼠,另外 3 个笼子里各有 5 只豚鼠。那么这家宠物店一共养了多少只啮齿动物呢?" ,
"input" : "" ,
"output" : "让我们一步步来思考。\n\n有 3 个笼子,每个笼子里有 10 只豚鼠。\n这意味着总共有 30 只豚鼠。\n\n还有 3 个笼子,每个笼子里有 5 只豚兔。\n这意味着总共有 15 只豚兔。\n\n现在我们可以把 30 和 15 加起来得到答案:\n\n总共有 45 只啮齿动物。\n因此,答案(阿拉伯数字)是 45。"
}
请自行准备 CoT 训练数据集,确保格式符合上述 JSONL 标准。
环境安装
昇腾 NPU + openMind Library 环境安装 使用昇腾 NPU 的话推荐在魔乐社区中找模型,里面能找到完成 NPU 适配的模型。魔乐社区使用的是 openMind Library 工具包,这个包支持在 Nvidia GPU 和 Ascend NPU 上运行,使用起来和 transformers 接口一致。如果说做昇腾 NPU 迁移的话非常推荐使用。
魔乐社区的模型分为 MindSpore 支持和 Pytorch-NPU 支持,这里主要看本地装什么环境,考虑到新手学习的话推荐使用 Pytorch-NPU,和 Pytorch 逻辑基本一致。
驱动安装&验证 首先得确定有 NPU 卡和 NPU 相关驱动,驱动是 8.0.RC3.beta1,具体可以参考软件安装-CANN 商用版 8.0.RC3 开发文档 - 昇腾社区。
安装好后的验证方法是运行下面的命令,该命令作用与 nvidia-smi 类似,这里是查看 NPU 的状态和性能:
openMind 环境搭建 openMind 环境安装比较简单,这边列出所需用到的全部安装命令:
wget https://download.pytorch.org/whl/cpu/torch-2.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
wget https://gitee.com/ascend/pytorch/releases/download/v6.0.rc3-pytorch2.4.0/torch_npu-2.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
pip3 install torch-2.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
pip3 install torch_npu-2.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
pip install openmind[pt]
pip install transformers accelerate datasets peft
pip install swanlab
Nvidia GPU + Transformers 环境安装 这个流程比较简单,首先也是得确保 Nvidia 驱动存在,验证命令:
如果没显示同样需要先安装 cuda 环境,这里贴上 CUDA 官方安装链接。网上有大量 cuda 安装教程,这里笔者就不赘述了。同样放出 transformers 环境安装的全部命令:
pip install torch
pip install transformers accelerate datasets peft
pip install swanlab
关于提示词模版构建(大坑) 这里需要强调一下,在使用 Qwen2.5 的 Instruct 模型微调时,为了保障效果建议严格按照模型自身的 Instruct 的提示词模版构建。HF Transformers 在 4.3 几的版本开始支持 Chat Templates。Qwen2.5 关于 Instruct 和 Chat 的提示词模版被直接写到了 tokenizers 的设置保存中,这导致了很多人在原始代码中找不到 instruct 提示词格式的构造。很多教程在教微调的时候还用的是 Qwen1 的老提示词模版或者自己构建的提示词模版,这会严重影响使用已经微调的模型做进一步微调时的效果 。建议针对模型微调时一定要仔细检查提示词模版的实现部分。尽量使用模型已经定义好的格式和结构。
可以在 Qwen 的 HF 项目中找到提示词模版,点击 HF Qwen 查看 chat_template 设置。chat_template 默认使用的是一种前端模版语言 jinja,并不好看懂,笔者把 qwen2.5 的提示词模版格式化后粘贴在下文:
{%- if tools %} {{- '<|im_start|>system\n' }} {%- if messages[0]['role'] == 'system' %} {{- messages[0]['content'] }} {%- else %} {{- 'You are Qwen, created by Alibaba Cloud. You are a helpful assistant.' }} {%- endif %} {{- '\n\n# Tools\n\nYou may call one or more functions to assist with the user query.\n\nYou are provided with function signatures within <tools></tools> XML tags:\n<tools>' }} {%- for tool in tools %} {{- '\n' }} {{- tool | tojson }} {%- endfor %} {{- '\n</tools>\n\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\n<tool_call>\n{"name": <function-name>, "arguments": <args-json-object>}\n</tool_call><|im_end|>\n' }} {%- else %} {%- if messages[0]['role'] == 'system' %} {{- '<|im_start|>system\n' + messages[0]['content'] + '<|im_end|>\n' }} {%- else %} {{- '<|im_start|>system\nYou are Qwen, created by Alibaba Cloud. You are a helpful assistant.<|im_end|>\n' }} {%- endif %} {%- endif %} {%- for message in messages %} {%- if (message.role == "user") or (message.role == "system" and not loop.first) or (message.role == "assistant" and not message.tool_calls) %} {{- '<|im_start|>' + message.role + '\n' + message.content + '<|im_end|>' + '\n' }} {%- elif message.role == "assistant" %} {{- '<|im_start|>' + message.role }} {%- if message.content %} {{- '\n' + message.content }} {%- endif %} {%- for tool_call in message.tool_calls %} {%- if tool_call.function is defined %} {%- set tool_call = tool_call.function %} {%- endif %} {{- '\n<tool_call>\n{"name": "' }} {{- tool_call.name }} {{- '", "arguments": ' }} {{- tool_call.arguments | tojson }} {{- '}\n</tool_call>' }} {%- endfor %} {{- '<|im_end|>\n' }} {%- elif message.role == "tool" %} {%- if (loop.index0 == 0) or (messages[loop.index0 - 1].role != "tool") %} {{- '<|im_start|>user' }} {%- endif %} {{- '\nresult\n' }} {{- message.content }} {{- '\nresult' }} {%- if loop.last or (messages[loop.index0 + 1].role != "tool") %} {{- '<|im_end|>\n' }} {%- endif %} {%- endif %} {%- endfor %} {%- if add_generation_prompt %} {{- '<|im_start|>assistant\n' }} {%- endif %}
可以看到超级长,因为定义了好几种情况,包括是否有 system prompt。以及针对 function tools 怎么处理等等等等。如果读不懂(我感觉大多数搞 deep learning 的除了做 LLM Finetune 也很小有机会去学一个前端语言)我建议用大模型给你逐行解释下,这里附上 jinja 的官方文档。
这里笔者简单提供我所使用的 Qwen2.5 简化版 python 模版(下脚本),去除了 Function Calling 和多轮对话的部分。并且只包含对 Instruct 和 Inputs 的处理部分,以及 Assistants 的生成头。这分为带 inputs 的版本和不带 inputs 的版本。我自己专门测试了使用此模版构造的提示词长度上和使用 Qwen 带 chat_template 的 tokenizers 完全一致。你只需要将 outputs 部分增加一个 \n<|im_end|>\n 即可直接拼接成 finetune LLM 模型的 targets 部分。
PROMPT_DICT = {
"prompt_no_input" : """<|im_start|>system\n{instruction}<|im_end|>\n<|im_start|>user\n<|im_end|>\n<|im_start|>assistant\n""" ,
"prompt_input" : """<|im_start|>system\n{instruction}<|im_end|>\n<|im_start|>user\n{input}<|im_end|>\n<|im_start|>assistant\n""" ,
}
如果你直接偷懒使用 chat_template 来 tokenizer 仅带 outputs 部分的数据。你会发现由于 Qwen 的 chat template 处理机制,实际上生成的 outputs 部分会默认带上 system prompts 。导致最后训练阶段会出现奇怪的内容。Qwen 的 tokenizers 针对未增加 system 角色的对话输入会自动加上如下提示词:
system:You are Qwen, created by Alibaba Cloud. You are a helpful assistant.
更神奇的是,这个 system prompt 居然是个英文的。Qwen 可是个中文模型。。。这个 system prompt 的出现会影响后续的模型微调效果。
可视化工具配置 (SwanLab 使用教程) SwanLab 可以将微调的许多关键参数自动记录下来并且能够在线或者离线保存 + 查看训练日志。SwanLab 同时支持记录 NVIDIA GPU 和华为昇腾 NPU 设备的日志记录工具 。最新版本已经支持对 NPU 的内存使用、功率、温度等进行记录。
关于 SwanLab 的使用方法可以参考 SwanLab 官方文档 - 快速开始。
对于 Huggingface Transformers 或者支持华为昇腾 NPU 的 openMind Library,可以使用 SwanLab Integration 轻松完成实验数据记录:
... from swanlab.integration.huggingface import SwanLabCallback swanlab_call = SwanLabCallback(
使用后不仅能进行多图表对比,更重要的是把一大堆的 huggingface transformers 的训练超参数全部记录下来了,简直调参党福音。
微调代码(多卡,支持华为 Ascend 卡) 下面附上完整的微调代码。在项目目录下创建 finetune.py 文件,并将如下代码粘贴进文件中:
import copy
import os
import io
import json
import logging
from dataclasses import dataclass, field, asdict
from typing import Dict , Optional , Sequence
import torch
from torch.utils.data import Dataset
try :
import openmind as tf_module
except :
import transformers as tf_module
import transformers
from peft import LoraConfig, get_peft_model
from swanlab.integration.huggingface import SwanLabCallback
IGNORE_INDEX = -100
PROMPT_DICT = {
"prompt_no_input" : """<|im_start|>system\n{instruction}<|im_end|>\n<|im_start|>user\n<|im_end|>\n<|im_start|>assistant\n""" ,
"prompt_input" : """<|im_start|>system\n{instruction}<|im_end|>\n<|im_start|>user\n{input}<|im_end|>\n<|im_start|>assistant\n""" ,
}
@dataclass
class ModelArguments :
model_name_or_path: Optional [str ] = field(
default="./weights/Qwen/Qwen2.5-7B-Instruct"
)
@dataclass
class DataArguments :
data_path: str = field(
default="./data/cot_train_cn.jsonl" ,
metadata={"help" : "Path to the training data." },
)
@dataclass
class TrainingArguments (transformers.TrainingArguments):
cache_dir: Optional [str ] = field(default=None )
optim: str = field(default="adamw_torch" )
model_max_length: int = field(
default=512 ,
metadata={
"help" : "Maximum sequence length. Sequences will be right padded (and possibly truncated)."
},
)
def _tokenize_fn (strings: Sequence [str ], tokenizer ) -> Dict :
"""Tokenize a list of strings."""
tokenized_list = [
tokenizer(
text,
return_tensors="pt" ,
padding="longest" ,
max_length=tokenizer.model_max_length,
truncation=True ,
)
for text in strings
]
input_ids = labels = [tokenized.input_ids[0 ] for tokenized in tokenized_list]
input_ids_lens = labels_lens = [
tokenized.input_ids.ne(tokenizer.pad_token_id).sum ().item()
for tokenized in tokenized_list
]
return dict (
input_ids=input_ids,
labels=labels,
input_ids_lens=input_ids_lens,
labels_lens=labels_lens,
)
def jload (f, mode="r" , jsonl=True ):
if not isinstance (f, io.IOBase):
with open (f, mode=mode, encoding="utf-8" ) as f:
if jsonl:
return [json.loads(line) for line in f if line.strip()]
else :
return json.load(f)
else :
if jsonl:
return [json.loads(line) for line in f if line.strip()]
else :
return json.load(f)
def preprocess (
sources: Sequence [str ],
targets: Sequence [str ],
tokenizer,
) -> Dict :
"""Preprocess the data by tokenizing."""
examples = [s + t for s, t in zip (sources, targets)]
examples_tokenized, sources_tokenized = [
_tokenize_fn(strings, tokenizer) for strings in (examples, sources)
]
input_ids = examples_tokenized["input_ids" ]
labels = copy.deepcopy(input_ids)
for label, source_len in zip (labels, sources_tokenized["input_ids_lens" ]):
label[:source_len] = IGNORE_INDEX
return dict (input_ids=input_ids, labels=labels)
class SupervisedDataset (Dataset ):
"""Dataset for supervised fine-tuning."""
def __init__ (self, data_path: str , tokenizer ):
super (SupervisedDataset, self ).__init__()
logging.warning("Loading data..." )
list_data_dict = jload(data_path)
logging.warning("Formatting inputs..." )
prompt_input, prompt_no_input = (
PROMPT_DICT["prompt_input" ],
PROMPT_DICT["prompt_no_input" ],
)
sources = [
(
prompt_input.format_map(example)
if example.get("input" , "" ) != ""
else prompt_no_input.format_map(example)
)
for example in list_data_dict
]
targets = [
f"{example['output' ]} \n{tokenizer.eos_token} \n"
for example in list_data_dict
]
logging.warning("Tokenizing inputs... This may take some time..." )
data_dict = preprocess(sources, targets, tokenizer)
try :
self .input_ids = data_dict["input_ids" ]
except KeyError as e:
raise KeyError("input_ids is invalid" ) from e
try :
self .labels = data_dict["labels" ]
except KeyError as e:
raise KeyError("labels is invalid" ) from e
def __len__ (self ):
return len (self .input_ids)
def __getitem__ (self, i ) -> Dict [str , torch.Tensor]:
return dict (input_ids=self .input_ids[i], labels=self .labels[i])
@dataclass
class DataCollatorForSupervisedDataset (object ):
"""Collate examples for supervised fine-tuning."""
tokenizer: object
def __call__ (self, instances: Sequence [Dict ] ) -> Dict [str , torch.Tensor]:
input_ids, labels = tuple (
[instance[key] for instance in instances] for key in ("input_ids" , "labels" )
)
input_ids = torch.nn.utils.rnn.pad_sequence(
input_ids, batch_first=True , padding_value=self .tokenizer.pad_token_id
)
labels = torch.nn.utils.rnn.pad_sequence(
labels, batch_first=True , padding_value=IGNORE_INDEX
)
return dict (
input_ids=input_ids,
labels=labels,
attention_mask=input_ids.ne(self .tokenizer.pad_token_id),
)
def make_supervised_data_module (tokenizer, data_args ) -> Dict :
"""Make dataset and collator for supervised fine-tuning."""
train_dataset = SupervisedDataset(
tokenizer=tokenizer, data_path=data_args.data_path
)
data_collator = DataCollatorForSupervisedDataset(tokenizer=tokenizer)
return dict (
train_dataset=train_dataset, eval_dataset=None , data_collator=data_collator
)
def train ():
parser = transformers.HfArgumentParser(
(ModelArguments, DataArguments, TrainingArguments)
)
model_args, data_args, training_args = parser.parse_args_into_dataclasses()
model = tf_module.AutoModelForCausalLM.from_pretrained(
model_args.model_name_or_path,
cache_dir=training_args.cache_dir,
trust_remote_code=True ,
)
lora_config = LoraConfig(
r=16 ,
lora_alpha=16 ,
target_modules=["q_proj" , "v_proj" ],
lora_dropout=0.1 ,
bias="none" ,
)
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()
tokenizer = tf_module.AutoTokenizer.from_pretrained(
model_args.model_name_or_path,
cache_dir=training_args.cache_dir,
model_max_length=training_args.model_max_length,
padding_side="right" ,
use_fast=False ,
trust_remote_code=True ,
)
data_module = make_supervised_data_module(tokenizer=tokenizer, data_args=data_args)
swanlab_call = SwanLabCallback(
"Ascend_finetune_v2" ,
experiment_name=os.path.basename(os.path.normpath(training_args.output_dir)),
config=asdict(data_args)
| asdict(model_args)
| asdict(training_args)
| asdict(lora_config),
public=True ,
)
trainer = tf_module.Trainer(
model=model,
tokenizer=tokenizer,
args=training_args,
callbacks=[swanlab_call],
**data_module,
)
trainer.train()
trainer.save_state()
trainer.save_model(output_dir=training_args.output_dir)
if __name__ == "__main__" :
train()
多卡训练的话可以使用 torchrun,这里附上一个启动多卡的 bash 脚本,在当前目录下创建 finetune.sh,并且粘贴如下脚本:
NPU_NUM=${1:-8}
EXP_NAME=$(basename "$0 " .sh)
if [ -d ./output ];then
rm -rf ./output/$EXP_NAME
mkdir -p ./output/$EXP_NAME
else
mkdir -p ./output/$EXP_NAME
fi
torchrun --nproc_per_node=$NPU_NUM --master_port=20248 finetune.py \
--model_name_or_path "./weights/Qwen/Qwen2.5-7B-Instruct" \
--data_path data/cot_train_cn.jsonl \
--bf16 True \
--output_dir ./output/$EXP_NAME \
--max_steps 2000 \
--per_device_train_batch_size 2 \
--eval_strategy "no" \
--save_strategy "steps" \
--save_steps 3000 \
--save_total_limit 1 \
--learning_rate 2e-5 \
--weight_decay 0. \
--warmup_ratio 0.03 \
--lr_scheduler_type "cosine" \
--seed 42 \
--logging_steps 10
bash finetune.sh <使用的 GPU/NPU 数量>
如果提示登录 swanlab,可以在官网完成注册后,使用获取 API KEY 找到对应的登陆密钥并粘贴,这样将能够使用云上看版随时查看训练过程与结果。
微调效果(附上 Gradio 代码) 本来准备了 Ceval 的测试结果,结果不知道为什么 Ascend 服务器连不上了,等过段时间更新下教程文档。
这里放出使用 CoT 数据微调 qwen-7b-instruct、qwen-0.5b-instruct 和使用 qwen-7b-instruct(8NPU)的 loss 结果。可以看到使用 8 个 NPU 能带来更好的训练 loss 表现和稳定性,哪怕在使用同样迭代数据量的情况下,8 个 NPU 依然能带来更好的 loss 结果。可能更大的 batch size 有助于模型稳定下降。
最后展现下使用 gradio 完成的官方 Qwen2.5-7B-Instruct、基于 Qwen2.5-7B 在中文 alpaca 数据集上指令微调、以及 cot 微调后的模型回复对比。可以看到 CoT 微调后模型确实具备了'step by step'的回复模式。
当然许多读者注意到了官方模型也展现出了'step by step'的回答模式,这主要是因为现在较新的模型在 finetune 数据集甚至 pretrain 数据集中就会预先加入 CoT 数据,所以模型在进行问答、尤其是数学题问答时,会展现出'步骤分解'的现象。笔者后续会尝试在较早期的 demo 中更新微调后的模型表现,并进一步验证其在复杂推理任务上的泛化能力。
使用 pip install gradio 安装依赖包
import gradio as gr
from openmind import AutoModelForCausalLM, pipeline
from peft import PeftModel
TOTAL_GPU_NUMS = 8
TOKENIZE_PATH = "~/weights/Qwen/Qwen2.5-7B-Instruct"
MODEL_LIST = {
"office_qwen7b" : "~/weights/Qwen/Qwen2.5-7B-Instruct" ,
"alpaca_qwen7b_lora" : "./projects/qwen_finietune_cot/output/qwen25-7B-alpaca" ,
"cot_qwen7b_lora" : "./projects/qwen_finietune_cot/output/qwen25-7Bi-cot" ,
}
model_names = MODEL_LIST.keys()
pipes = dict ()
for i, model_name in enumerate (model_names):
save_path = MODEL_LIST[model_name]
model = AutoModelForCausalLM.from_pretrained(save_path)
if model_name[:-5 ] == "_lora" :
model = PeftModel.from_pretrained(model, save_path)
pipe = pipeline(
"text-generation" ,
model=model,
tokenizer=TOKENIZE_PATH,
framework="pt" ,
device=f"npu:{i%TOTAL_GPU_NUMS} " ,
)
pipes[model_name] = pipe
def generate_response (instruct_text, input_text ):
messages = [
{
"role" : "system" ,
"content" : instruct_text,
},
{
"role" : "user" ,
"content" : input_text,
},
]
outputs = [
pipes[model_name](messages, max_new_tokens=256 )[-1 ]["content" ]
for model_name in model_names
]
return tuple (outputs)
demo = gr.Interface(
fn=generate_response,
inputs=[
gr.Textbox(label="instruction" ),
gr.Textbox(label="input" ),
],
outputs=[gr.Textbox(label=model_name) for model_name in model_names],
)
if __name__ == "__main__" :
demo.launch()
总结与最佳实践 通过本教程的实践,我们成功实现了 Qwen2.5 模型的思维链(CoT)微调。以下是几点关键经验总结:
提示词模板至关重要 :务必使用模型官方提供的 chat_template,避免自定义模板导致的 System Prompt 缺失或格式错误,这直接影响推理效果。
数据质量决定上限 :虽然使用了自动化翻译的数据集进行练手,但在生产环境中,建议使用人工审核的高质量 CoT 数据,特别是针对特定垂直领域的推理任务。
多卡并行加速训练 :使用 torchrun 配合 LoRA 技术可以显著降低显存占用并提升训练速度,特别是在处理长序列 CoT 数据时,多卡并行显得尤为重要。
监控与可视化 :集成 SwanLab 等工具可以有效追踪训练过程中的 Loss 变化及硬件资源使用情况,便于及时调整超参数。
未来,随着大模型推理能力的进一步提升,CoT 微调将成为提升模型逻辑推理能力的重要手段。开发者应关注数据构建的多样性,并结合实际业务场景进行针对性优化。
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
RSA密钥对生成器 生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
Mermaid 预览与可视化编辑 基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
随机西班牙地址生成器 随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online
Gemini 图片去水印 基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
curl 转代码 解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online