原文链接:从零开始的DeepSeek微调训练实战(SFT)
微调参考示例:由unsloth官方提供https://colab.research.google.com/github/unslothai/notebooks/blob/main/nb/Qwen2.5_(7B)-Alpaca.ipynbhttps://colab.research.google.com/github/unslothai/notebooks/blob/main/nb/Qwen2.5_(7B)-Alpaca.ipynb
本文使用modelscope社区提供的免费GPU示例进行复现。
魔搭社区汇聚各领域最先进的机器学习模型,提供模型探索体验、推理、训练、部署和应用的一站式服务。https://www.modelscope.cn/my/overview
基础概念
预训练模型 (Pre-trained Model): 预训练模型是指在大规模数据集上(如Wikipedia、书籍、网页等)进行过训练的模型。这些模型学习到了通用的语言知识和模式。你可以把它们想象成已经掌握了基本语法和常识的“学生”。常见的预训练模型有BERT、GPT、Llama、DeepSeek等。
微调 (Fine-tuning): 微调是指在预训练模型的基础上,使用特定任务的数据集继续训练模型,使其适应特定任务或领域。就像让一个已经掌握基本知识的学生,学习特定专业的知识。
为什么需要微调?: 预训练模型虽然强大,但它们是通用的。对于特定任务(如医疗问答、代码生成、情感分析等),预训练模型可能表现不佳。微调可以让模型更好地适应特定任务,提高性能。
SFT (Supervised Fine-Tuning): SFT是一种微调方法,它使用带有标签的数据集进行训练。例如,在医疗问答任务中,数据集会包含问题和对应的正确答案。模型通过学习这些问题和答案之间的关系,来提高在特定任务上的表现。
SFT vs. RLHF:
- SFT (Supervised Fine-tuning): 使用标注好的数据集进行训练。模型学习输入和输出之间的直接映射。简单高效,但依赖于高质量的标注数据。
- RLHF (Reinforcement Learning from Human Feedback): 通过人类反馈来训练模型。首先使用SFT,然后通过人类对模型输出进行打分,并使用强化学习算法来优化模型。可以更好地捕捉人类偏好,但更复杂,成本更高。
- 总结: SFT是基础,RLHF是进阶。通常先进行SFT,再根据需要进行RLHF。
高效微调 (Efficient Fine-tuning): 高效微调是指在有限的计算资源下,对大型模型进行微调的方法。例如,LoRA(Low-Rank Adaptation)只微调模型中的部分参数,从而减少计算量和内存需求。
环境准备
unsloth
-
Unsloth 是什么?
Unsloth 是一个专为大型语言模型(LLM)微调和推理设计的框架。它的主要目标是提高训练速度和降低内存消耗,让用户能够在有限的硬件资源上更高效地进行 LLM 的操作。
-
Unsloth 的主要特点
-
速度快:Unsloth 通过各种优化技术(如 Flash Attention、量化等)显著提高了 LLM 的训练和推理速度。在某些情况下,速度提升可达数倍。
-
内存占用低:Unsloth 通过优化内存使用,使得在较小显存的 GPU 上也能微调大型模型。
-
易于使用:Unsloth 提供了简洁的 API,方便用户快速上手。
-
支持多种模型:Unsloth 支持多种流行的 LLM,如 Llama、Mistral、Phi、Qwen 等。
-
-
Unsloth 的安装
-
直接使用pip命令安装即可
-
pip install unsloth pip install --force-reinstall --no-cache-dir --no-deps git+https://github.com/unslothai/unsloth.git
-
WandB (Weights & Biases) 安装
-
WandB 是什么?
WandB 是一个用于机器学习实验跟踪、可视化和协作的平台。它可以帮助你记录实验的各种指标、超参数、模型权重、数据集等,并提供交互式的可视化界面,方便你分析实验结果和比较不同实验的表现。
-
WandB 的主要特点
-
实验跟踪:记录实验的各种指标(如损失、准确率、学习率等)、超参数、代码版本、数据集等。
-
可视化:提供交互式的图表,方便你分析实验结果。
-
协作:支持多人协作,方便团队成员共享实验结果和讨论。
-
超参数优化:支持自动超参数搜索,帮助你找到最佳的超参数组合。
-
模型管理:可以保存和版本控制模型权重。
-
报告生成:可以自动生成实验报告。
-
-
WandB 的安装和注册
-
安装:使用 pip 安装 WandB:
-
pip install wandb
- 注册:
-
访问 WandB 官网(https://wandb.ai/site)并注册账号。
-
注册后,在你的个人设置页面找到 API Key,复制它。
-
WandB 在环境准备中的作用
在 SFT 环境准备中,WandB 主要用于:
-
监控训练过程:在训练过程中,WandB 会自动记录各种指标,如损失、学习率等,并提供实时更新的图表。
-
记录超参数:WandB 会记录你使用的超参数,方便你后续复现实验和比较不同超参数的效果。
-
保存模型:你可以使用 WandB 保存训练过程中的模型权重,方便后续加载和使用。
-
分析实验结果:WandB 提供了丰富的可视化工具,可以帮助你分析实验结果,找出最佳的模型和超参数。
-
-
-
模型下载
ModelScope模型地址:https://www.modelscope.cn/models/deepseek-ai/DeepSeek-R1-Distill-Qwen-7B
创建DeepSeek-R1-Distill-Qwen-7B文件夹,用于保存下载的模型权重:
-
mkdir ./DeepSeek-R1-Distill-Qwen-7B
创建成功后,可使用如下命令下载模型:
-
modelscope download --model deepseek-ai/DeepSeek-R1-Distill-Qwen-7B --local_dir ./DeepSeek-R1-Distill-Qwen-7B
- 模型权重文件
- config.json
- 内容: 这个文件包含了模型的配置信息,它是一个 JSON 格式的文本文件。这些配置信息定义了模型的架构、层数、隐藏层大小、注意力头数等。
- 重要性: 这是模型的核心配置文件,加载模型时会读取这个文件来构建模型的结构。
{"architectures": ["Qwen2ForCausalLM" // 指定模型的架构类型为 Qwen2ForCausalLM,这是一个用于因果语言建模(生成文本)的 Qwen2 模型。],"attention_dropout": 0.0, // 在注意力机制中使用的 dropout 比率。设置为 0.0 表示不使用 dropout。Dropout 是一种正则化技术,用于防止过拟合。"bos_token_id": 151646, // 句子开头标记(Beginning of Sentence)的 ID。在分词器中,每个词或标记都有一个唯一的 ID。"eos_token_id": 151643, // 句子结束标记(End of Sentence)的 ID。"hidden_act": "silu", // 隐藏层的激活函数。SiLU(Sigmoid Linear Unit)是一种激活函数。"hidden_size": 3584, // 隐藏层的大小(维度)。"initializer_range": 0.02, // 用于初始化模型权重的标准差。"intermediate_size": 18944, // 前馈网络(Feed-Forward Network)中间层的大小。"max_position_embeddings": 131072, // 模型可以处理的最大序列长度(位置嵌入的数量)。"model_type": "qwen2", // 模型类型为 qwen2。"num_attention_heads": 28, // 注意力机制中注意力头的数量。"num_hidden_layers": 28, // 模型中隐藏层(Transformer 层)的数量。"num_key_value_heads": 4, // 键值头的数量。用于分组查询注意力(Grouped-Query Attention, GQA)。如果该值小于`num_attention_heads`,则表示启用了GQA, 否则为多头注意力(Multi-Head Attention, MHA)。"rms_norm_eps": 1e-06, // RMSNorm(Root Mean Square Layer Normalization)中使用的 epsilon 值,用于防止除以零。"rope_theta": 10000.0, // RoPE(Rotary Positional Embeddings)中使用的 theta 值。RoPE 是一种位置编码方法。"tie_word_embeddings": false, // 是否将词嵌入矩阵和输出层的权重矩阵绑定(共享)。设置为 `false` 表示不绑定。"torch_dtype": "bfloat16", // 模型使用的默认数据类型。`bfloat16` 是一种 16 位浮点数格式,可以提高计算效率并减少内存占用。"transformers_version": "4.48.3", // 使用的 Transformers 库的版本。"use_cache": true, // 是否使用缓存机制来加速推理。设置为 `true` 表示使用缓存。"vocab_size": 152064 // 词汇表的大小(不同词或标记的数量)。
}
- configuration.json
- 内容: 这个文件和 config.json 类似,通常包含模型的配置信息。在某些模型中,这两个文件可能是同一个文件,或者 configuration.json 包含了更详细的配置。对于 DeepSeek-R1-Distill-7B 模型,你可以认为它和 config.json 作用相同。
- generation_config.json
- 内容: 这个文件包含模型生成文本时的配置参数,例如解码方法(beam search、top-k sampling 等)、最大生成长度、温度系数等。
{"_from_model_config": true, // 表示这些配置中的大部分是从模型的配置文件(config.json)中继承的。"bos_token_id": 151646, // 句子开头标记(Beginning of Sentence)的 ID。"eos_token_id": 151643, // 句子结束标记(End of Sentence)的 ID。"do_sample": true, // 是否使用采样(sampling)方法生成文本。如果设置为 `false`,则使用贪婪解码(greedy decoding)。"temperature": 0.6, // 温度系数。温度系数用于控制生成文本的随机性。值越高,生成的文本越随机;值越低,生成的文本越确定。"top_p": 0.95, // Top-p 采样(nucleus sampling)的阈值。Top-p 采样只从概率最高的、累积概率超过 `top_p` 的词中进行采样。"transformers_version": "4.39.3" // 使用的 Transformers 库的版本。(原文档中是4.39.3,这与之前config.json里的版本号不同,但通常情况下,版本号应当以config.json里的为准)
}
-
LICENSE
内容: 这是一个文本文件,包含了模型的许可证信息。许可证规定了你可以如何使用、修改和分发模型。 -
model-00001-of-00002.safetensors 和 model-00002-of-00002.safetensors
内容: 这些文件是模型权重文件,它们以 Safetensors 格式存储。Safetensors 是一种安全且高效的张量存储格式。由于模型很大,权重被分成了多个文件。 -
model.safetensors.index.json
内容: 这是一个索引文件,用于指示哪些权重存储在哪个 .safetensors 文件中。当模型权重被分成多个文件时,需要这个索引文件来正确加载权重。 -
README.md
内容: 这是一个 Markdown 格式的文本文件,通常包含模型的介绍、使用说明、示例代码等。 -
tokenizer_config.json
内容: 包含分词器(Tokenizer)的配置信息。
{"add_bos_token": true, // 是否在输入序列的开头添加句子开头标记(BOS token)。设置为 `true` 表示添加。"add_eos_token": false, // 是否在输入序列的结尾添加句子结束标记(EOS token)。设置为 `false` 表示不添加。"__type": "AddedToken", //这是一个内部使用的类型标记,表示这是一个"添加的token""content": "<|begin of sentence|>", // 这是一个特殊标记的内容,表示句子的开始"lstrip": false, //在处理这个token时,是否移除左侧的空白符"normalized": true, // 是否对这个token进行标准化处理"rstrip": false, //是否移除右边的空白符"single_word": false, // 是否将此token视为单个词"clean_up_tokenization_spaces": false, // 是否清理分词过程中的空格。设置为 `false` 表示不清理。"__type": "AddedToken","content": "<|end of sentence|>","lstrip": false,"normalized": true,"rstrip": false,"single_word": false,"legacy": true, // 是否使用旧版(legacy)的分词器行为。这里设置为`true`可能表示兼容旧版本。"model_max_length": 16384, // 模型可以处理的最大序列长度。"__type": "AddedToken","content": "<|end of sentence|>","lstrip": false,"normalized": true,"rstrip": false,"single_word": false,"sp_model_kwargs": {}, // SentencePiece 模型的相关参数(这里为空)。"unk_token": null, // 未知词标记(unknown token)。设置为 `null` 表示没有专门的未知词标记。"tokenizer_class": "LlamaTokenizerFast", // 分词器的类名。`LlamaTokenizerFast` 表示这是一个快速版本的 Llama 分词器。"chat_template": "{% if not add_generation_prompt is defined %}{% set add_generation_prompt = false %}{% endif %}{% set ns = namespace(is_first=false, is_tool=false, is_output_first=true, system_prompt='') %}{%- for message in messages %}{%- if message['role'] == 'system' %}{% set ns.system_prompt = message['content'] %}{%- endif %}{%- endfor %}{{bos_token}}{{ns.system_prompt}}{%- for message in messages %}{%- if message['role'] == 'user' %}{%- set ns.is_tool = false -%}{{'<|User|>' + message['content']}}{%- endif %}{%- if message['role'] == 'assistant' and message['content'] is none %}{%- set ns.is_tool = false -%}{%- for tool in message['tool_calls']%}{%- if not ns.is_first %}{{'<|Assistant|><|tool calls begin|><|tool call begin|>' + tool['type'] + '<|tool sep|>' + tool['function']['name'] + '\n' + '```json' + '\n' + tool['function']['arguments'] + '\n' + '```' + '<|tool call end|>'}}{%- set ns.is_first = true -%}{%- else %}{{'\n' + '<|tool call begin|>' + tool['type'] + '<|tool sep|>' + tool['function']['name'] + '\n' + '```json' + '\n' + tool['function']['arguments'] + '\n' + '```' + '<|tool call end|>'}}{{'<|tool calls end|><|end of sentence|>'}}{%- endif %}{%- endfor %}{%- endif %}{%- if message['role'] == 'assistant' and message['content'] is not none %}{%- if ns.is_tool %}{{'<|tool outputs end|>' + message['content'] + '<|end of sentence|>'}}{%- set ns.is_tool = false -%}{%- else %}{% set content = message['content'] %}{% if '</think>' in content %}{% set content = content.split('</think>')[-1] %}{% endif %}{{'<|Assistant|>' + content + '<|end of sentence|>'}}{%- endif %}{%- endif %}{%- if message['role'] == 'tool' %}{%- set ns.is_tool = true -%}{%- if ns.is_output_first %}{{'<|tool outputs begin|><|tool output begin|>' + message['content'] + '<|tool output end|>'}}{%- set ns.is_output_first = false %}{%- else %}{{'\n<|tool output begin|>' + message['content'] + '<|tool output end|>'}}{%- endif %}{%- endif %}{%- endfor -%}{% if ns.is_tool %}{{'<|tool outputs end|>'}}{% endif %}{% if add_generation_prompt and not ns.is_tool %}{{'<|Assistant|><think>\n'}}{% endif %}"// ↑这是一个 Jinja2 模板,定义了对话的格式。它根据消息的角色(用户、助手、工具)和内容,构建最终的输入文本。}
数据集准备
推理模型与通用模型相比,输出的回答包括了一段思考过程(Chain of Thoughts 思维链)。这个思考过程本质也是通过预测下一个token进行实现的,只不过DeepSeek系列模型输出时,会将思考过程放在一对特殊token <think>...</think>之间,</think>标签后的内容作为回答的正文。
微调推理模型时,同样需要包含思维链和最终回答两部份。因此,在围绕DeepSeek R1 Distill模型组进行微调的时候,微调数据集的回复部分文本也需要是包含推理 和最终回复两部分内容,才能使得DeepSeek R1模型组在保持既定回复风格的同时,强化模型能力,反之则会导致指令消融问题(模型回复不再包含think部分)。
modelscope社区提供了多样的推理数据集供开发者使用 。
原文采取由深圳大数据研究院发布的HuatuoGPT-o1模型的微调数据集—medical-o1-reasoning-SFT,地址:https://www.modelscope.cn/datasets/AI-ModelScope/medical-o1-reasoning-SFT。
本数据集将数据分为:
- Question:医疗问题
- Complex_CoT:进行诊疗的思维链
- Response:最终的答复
数据集中所有内容均为英文
模型演示
在进行微调前,我们可以了解一下模型的基本用法。
1. 加载已经下载到本地的模型
max_seq_length = 2048 # 指定输出的最大长度
dtype = None # 不指定模型精度,由unsloth框架自动检测
load_in_4bit = False # 采用int4量化,减少显存占用,但是会降低模型性能# 加载模型和分词器
model, tokenizer = FastLanguageModel.from_pretrained(model_name="./DeepSeek-R1-Distill-Qwen-7B", # 待微调的模型名称max_seq_length=max_seq_length, # 模型可以处理的最长序列长度dtype=dtype, # 限定模型浮点精度load_in_4bit=False # 是否使用int量化
)
2. 通过unsloth框架配置待微调的LoRA模型
'''
LoRA 的核心思想是,对于预训练模型的权重矩阵 W,不直接对其进行更新,
而是添加一个低秩分解矩阵 ΔW = A * B,
其中 A 和 B 是两个较小的矩阵。在微调过程中,只更新 A 和 B 的参数,而 W 的参数保持不变。
这样可以大大减少需要微调的参数数量,降低计算成本。
'''
model = FastLanguageModel.get_peft_model(model,r=8, # lora微调的秩 # 较小的 `r` 值会减少需要微调的参数数量,降低计算成本,但也可能降低模型的表达能力。# 较大的 `r` 值会增加参数数量,提高模型的表达能力,但也会增加计算成本。# 通常需要根据实际情况进行实验,选择合适的 `r` 值。一般来说,8、16、32、64 是常用的值。target_modules = ["q_proj", "k_proj", "v_proj", # 指定要应用 LoRA 的模块。这些模块通常是 Transformer 模型中的线性层。"o_proj", "gate_proj", "up_proj", "down_proj"], # 这里分别应用了注意力机制中的Wq, Wk, Wv, Wo线性投影层,FFN中的线性层lora_alpha=8, # lora缩放因子,决定模型权重的更新程度,建议设置为r或r的倍数lora_dropout=0,bias="none", # 不为LoRA层添加偏置use_gradient_checkpointing="unsloth", # 是否设置梯度检查点,# 梯度检查点是一种以时间换空间的技术,可以减少内存占用,但会增加计算时间。random_state=3407, # 设置随机种子,保证实验可以浮现use_rslora=False, # 是否使用Rank-Stabilized LoRA(rslora)。rslora 是一种改进的 LoRA 方法,可以自动调整 `lora_alpha`。loftq_config=None # 是否使用QLoRA,即将LoRA与量化技术结合
)
3. 进行简单推理
# 将模型切换为推理模式,可以进行简单的对话
FastLanguageModel.for_inference(model)question = "请介绍一下你自己!"
# 对输入进行分词
# 传入待分词的文本列表,最后返回一个PyTorch张量
input_ids = tokenizer([question], return_tensors="pt").to("cuda")
# input_ids返回token对应词表中的id,即将一个句子映射为一个token id序列
# attention_mask用于表示input_ids中哪些是为了填充序列长度而通过<pad>填充的token,1表示所有的 token 都是实际的词或标记,没有填充。
input_ids# 调用模型生成答复
'''
是否使用缓存机制来加速生成过程。设置为 True 表示使用缓存。缓存机制会存储先前计算的键/值对(key/value pairs),避免重复计算,从而提高生成速度。在自回归生成(逐个 token 生成)中,缓存机制非常有用。
'''
outputs_ids = model.generate(input_ids=input_ids.input_ids,max_new_tokens=1024,use_cache=True
)
# 模型的直接输出同样为token ids,需要通过tokenizer进行解码
outputs_idsresponse = tokenizer.batch_decode(outputs_ids)
print(response[0])
可以在prompt中添加<think>标签对引导模型进行思考。
question = "你好,好久不见!"
# 更完善的prompt
prompt_style_chat = """请写出一个恰当的回答来完成当前对话任务。### Instruction:
你是一名助人为乐的助手。### Question:
{}### Response:
"""# 使用tokenizer处理prompt
input_ids = tokenizer([prompt_style_chat.format(questionm '')], return_tensors="pt").to("cuda")outputs = model.generate(input_ids=input_ids.input_ids,use_cache=True,do_sample=True, # 启用采样temperature=0.7, # 较高的温度top_p=0.9, # Top-p 采样repetition_penalty=1.2, # 重复惩罚max_new_tokens=1024, # 最大新token数量
)response = tokenizer.batch_decode(outputs, skip_special_tokens=True)[0]
print(response)
未添加<think>标签,模型有概率不思考。
prompt_style_chat = """请写出一个恰当的回答来完成当前对话任务。### Instruction:
你是一名助人为乐的助手。### Question:
{}### Response:
<think>{}
"""
question = "请你分析李朗笛和朗朗以及李云迪之间的关系"# 使用tokenizer处理prompt
input_ids = tokenizer([prompt_style_chat.format(question, "")], return_tensors="pt").to("cuda")outputs = model.generate(input_ids=input_ids.input_ids,use_cache=True,do_sample=True, # 启用采样temperature=0.7, # 较高的温度top_p=0.95, # Top-p 采样repetition_penalty=1.2, # 重复惩罚max_new_tokens=1024, # 最大新token数量
)response = tokenizer.batch_decode(outputs, skip_special_tokens=False)[0]
print(response)
添加<think>标签作为引导,模型更容易进行思考。
微调实操
微调请重新开一个notebook,清空缓存,从头进行。
1. 倒入依赖
# 导入依赖
from modelscope.msdatasets import MsDataset # modelscope数据集类
from trl import SFTTrainer # 微调训练器配置类
from transformers import TrainingArguments # 微调参数配置类
from unsloth import FastLanguageModel, is_bfloat16_supported # 检查GPU是否支持bf16
import wandb # 微调数据可视化
2. 定义模板
因为数据集是英文的,所以promt也采用英文,保证语言一致性。
# 1. 定义prompt模板
finetune_template = '''Below is an instruction that describes a task, paired with an input that provides further context.
Write a response that appropriately completes the request.
Before answering, think carefully about the question and create a step-by-step chain of thoughts to ensure a logical and accurate response.### Instruction:
You are a medical expert with advanced knowledge in clinical reasoning, diagnostics, and treatment planning.
Please answer the following medical question. ### Question:
{}### Response:
<think>
{}
</think>
{}'''prompt_style_zh = '''以下是一个任务说明,配有提供更多背景信息的输入。
请写出一个恰当的回答来完成该任务。
在回答之前,请仔细思考问题,并按步骤进行推理,确保回答逻辑清晰且准确。### Instruction:
您是一位具有高级临床推理、诊断和治疗规划知识的医学专家。
请回答以下医学问题。### 问题:
{}### 问题:
<think>{}'''
3. 加载模型
# 2. 加载模型
model, tokenizer = FastLanguageModel.from_pretrained(model_name="./DeepSeek-R1-Distill-Qwen-7B",max_seq_length=2048,dtype=None,load_in_4bit=False
)
EOS_TOKEN = tokenizer.eos_token
4. 加载数据集
# 在模型微调时,给微调数据集加上 EOS_TOKEN 非常重要。它可以明确文本边界、保持训练目标一致性、控制生成过程、处理多轮对话,以及更好地利用 CoT 数据集。
EOS_TOKEN = tokenizer.eos_token# 格式话训练数据
def formatting_prompts_func(examples):inputs = examples["Question"]cots = examples["Complex_CoT"]outputs = examples["Response"]texts = []for input, cot, output in zip(inputs, cots, outputs):text = finetune_template.format(input, cot, output) + EOS_TOKENtexts.append(text)return {"text": texts,}ds = MsDataset.load('AI-ModelScope/medical-o1-reasoning-SFT', split = "train")
dataset = ds.map(formatting_prompts_func, batched = True,)
print(dataset["text"][0])
5. 配置微调模型
将LoRA模块加入模型,为微调做准备
'''
LoRA 的核心思想是,对于预训练模型的权重矩阵 W,不直接对其进行更新,
而是添加一个低秩分解矩阵 ΔW = A * B,
其中 A 和 B 是两个较小的矩阵。在微调过程中,只更新 A 和 B 的参数,而 W 的参数保持不变。
这样可以大大减少需要微调的参数数量,降低计算成本。
'''
model = FastLanguageModel.get_peft_model(model,r=16, # lora微调的秩 # 较小的 `r` 值会减少需要微调的参数数量,降低计算成本,但也可能降低模型的表达能力。# 较大的 `r` 值会增加参数数量,提高模型的表达能力,但也会增加计算成本。# 通常需要根据实际情况进行实验,选择合适的 `r` 值。一般来说,8、16、32、64 是常用的值。target_modules = ["q_proj", "k_proj", "v_proj", # 指定要应用 LoRA 的模块。这些模块通常是 Transformer 模型中的线性层。"o_proj", "gate_proj", "up_proj", "down_proj"], # 这里分别应用了注意力机制中的Wq, Wk, Wv, Wo线性投影层,FFN中的线性层lora_alpha=16, # lora缩放因子,决定模型权重的更新程度,建议设置为r或r的倍数lora_dropout=0,bias="none", # 不为LoRA层添加偏置use_gradient_checkpointing="unsloth", # 是否设置梯度检查点,# 梯度检查点是一种以时间换空间的技术,可以减少内存占用,但会增加计算时间。random_state=3407, # 设置随机种子,保证实验可以浮现use_rslora=False, # 是否使用Rank-Stabilized LoRA(rslora)。rslora 是一种改进的 LoRA 方法,可以自动调整 `lora_alpha`。loftq_config=None # 是否使用QLoRA,即将LoRA与量化技术结合
)
6. 配置微调参数
# 5. 配置微调参数
trainer = SFTTrainer(model=model,tokenizer=tokenizer,train_dataset=dataset,dataset_text_field="text", # 数据集中包含文本的字段的名称。# dataset_text_field="text", # 说明text列对应的是微调数据集max_seq_length=2048, # 模型能处理的最长序列dataset_num_proc=2, # 用于预处理数据的进程数。args=TrainingArguments(per_device_train_batch_size=2, # mini-batch-sizegradient_accumulation_steps=4, # 梯度累积,用于模型batch_size=2*4=8的情况,模型实际上经过 2 * 4 = 8 个batch之后才会更新参数(一个step),能缓解GPU无法放下大batch的问题num_train_epochs=3, # 训练轮数# max_steps = 60 # 如果要进行迅速严重微调可行性,可以只训练60个steps,训练的总步数(参数更新次数)。warmup_steps=5, # 模型热身步数,学习率会从 0 逐渐增加到设定的学习率。lr_scheduler_type="linear", # 学习率调度器类型。这里使用线性调度器,学习率会线性下降。learning_rate=2e-4, # 学习率fp16=not is_bfloat16_supported(), # 是否使用 FP16(16 位浮点数)混合精度训练。如果 GPU 不支持 bfloat16,则使用 fp16。bf16=is_bfloat16_supported(),logging_steps=10, # 多少个step打印一次信息optim="adamw_8bit", # 指定优化器weight_decay=0.01, # 权重衰退seed=3407, # 随机种子,保证结果可以复现output_dir="outputs" # 保存训练结果(模型、日志等)的目录。)
)
7. 进行微调
# 6. 进行微调
wandb.init()
trainer_stats = trainer.train()
看到如下输出即表示微调正在运行中。
8. 将LoRA权重与原始矩阵合并,保存微调后的模型
# LoRA微调完成后,保存微调模型并合并矩阵
new_model_local = "DeepSeek-R1-Qwen-7B-Medical-Full" # 定义一个字符串变量,表示保存模型的本地路径。
model.save_pretrained(new_model_local) # 保存微调后的模型(包括 LoRA 权重)。
tokenizer.save_pretrained(new_model_local) # 保存分词器。
model.save_pretrained_merged(new_model_local, tokenizer, save_method="merged_16bit") # 合并 LoRA 权重到基础模型中,并保存合并后的模型。