一.lora_model文件夹
会保存整个微调后模型的权重和配置文件,而不仅仅是LoRA调整的部分。
这个目录将包含模型的所有权重、配置信息(如模型架构、超参数等),以及分词器的词汇表和其他必要信息。
lora_model
保存的是经过微调的模型和分词器的相关信息。具体来说,保存的内容包括:
-
模型权重:微调后的模型的参数,这些权重会被保存到指定的目录中,以便于后续加载和使用。
-
分词器配置:分词器的配置和词汇表,确保在进行文本处理时能够正确地编码和解码输入文本。
这些文件通常会包含以下内容:
- 模型权重文件:例如,PyTorch格式的权重文件(通常是
.bin
文件)。 - 配置文件:包含模型结构和超参数设置的配置文件(例如,
config.json
)。 - 分词器文件:包括分词器的配置和词汇表,
vocab.json
和merges.txt
等文件。
保存这些文件的目的是为了方便后续加载和推理。
住:这里lora_model文件信息应该只包含LoRA权重配置信息。但是基础模型的权重仍然存在,只是没有被保存到 lora_model
文件夹中(感觉应该类似于调用model文件夹里的配置信息那样,只是调用lora_model里的配置信息即可加载微调后的所有模型信息配置和分词器信息配置)。基础模型的权重通常保存在 ./model/Qwen2-0.5B-Instruct
路径下。
二.model文件夹
1.总体概述
- 模型文件(如
model.safetensors
):- 作用:这个文件包含了模型的实际权重和参数,是模型的核心组成部分。在训练完成后,这些权重被保存下来,以便在需要时进行模型的加载和推断。
- 重要性:没有这些权重,模型就无法进行任何预测或生成任务。
- 配置文件(如
config.json
):- 作用:这个文件包含了模型的配置信息,如模型架构、层数、激活函数等关键设置。它是加载和正确使用模型所必需的。
- 重要性:配置文件确保了模型在不同环境或平台上的可移植性和一致性。
- 生成器配置文件(如
generation_config.json
):- 作用:这个文件专门用于配置文本生成过程的参数,如最大生成长度、温度(影响生成文本的多样性)、前缀文本等。它允许用户根据具体需求调整生成文本的质量和风格。
- 重要性:对于需要生成高质量文本的NLP任务(如文本摘要、机器翻译等),生成器配置文件是不可或缺的。
- 合并文件(如
merges.txt
):- 作用:这个文件在基于字节对编码(BPE)或类似技术的分词器中常见,它定义了如何将较小的单元(如字符)合并成更大的单元(如子词)。这对于将文本转换为模型可以处理的序列至关重要。
- 重要性:合并文件有助于减少词汇表的规模,同时保持对文本的有效表示。
- 特殊令牌映射文件(如
special_tokens_map.json
):- 作用:这个文件映射了模型中的特殊标记(如开始标记、结束标记、填充标记等)到词汇表中的索引。这些特殊标记在模型训练和推断过程中起着重要作用。
- 重要性:特殊令牌映射文件确保了模型能够正确处理这些特殊标记,从而生成符合预期的文本。
- Tokenizer文件(如
tokenizer.json
):- 作用:这个文件包含了分词器的配置和定义。分词器是将原始文本转换为模型可以处理的数字序列的关键步骤。
- 重要性:Tokenizer文件确保了文本数据的一致性和准确性,是模型训练和推断过程中的重要环节。
- Tokenizer配置文件(如
tokenizer_config.json
):- 作用:这个文件提供了分词器的额外配置信息,如词汇表大小、分词策略等。它有助于用户根据具体任务调整分词器的行为。
- 重要性:Tokenizer配置文件为用户提供了更多的灵活性和控制力,以适应不同的NLP任务和数据集。
- vocab.json 词汇表(可能隐含在Tokenizer文件中或单独存在):
- 作用:词汇表是模型能够理解的单词和标记的集合。在分词过程中,文本被转换为词汇表中单词和标记的序列。
- 重要性:词汇表是模型理解和生成文本的基础。一个全面且准确的词汇表对于提高模型的性能至关重要。
2.部分文件代码重要参数解读
(1)added_tokens.json
{
"<|endoftext|>": 151643,
"<|im_end|>": 151645,
"<|im_start|>": 151644
}
"<|endoftext|>": 151643
- 作用:这个特殊标记通常用于指示文本序列的结束。在文本生成任务中,当模型遇到这个标记时,它知道已经到达了序列的末尾,可以停止生成。这个标记在处理长文本或批量文本时特别有用,因为它允许模型明确地区分不同的文本序列。
- 含义:
151643
是这个特殊标记在模型词汇表中的唯一标识符(ID)。词汇表是一个包含模型所有可识别单词和标记的列表,每个单词或标记都有一个与之对应的唯一ID。
"<|im_end|>": 151645
- 作用:这个特殊标记用于指示图像描述或类似任务中图像描述部分的结束。在图像到文本(image-to-text)的生成任务中,模型可能需要知道何时停止生成与特定图像相关的文本。这个标记就起到了这样的作用。
- 含义:
151645
是这个特殊标记在模型词汇表中的唯一ID。
"<|im_start|>": 151644
- 作用:与
"<|im_end|>"
相反,这个特殊标记用于指示图像描述或类似任务中图像描述部分的开始。在模型开始为特定图像生成描述之前,它可以接收到这个标记作为输入,从而知道接下来要生成的内容是与该图像相关的。 - 含义:
151644
是这个特殊标记在模型词汇表中的唯一ID。
- 作用:与
(2)config.json
{
"_name_or_path": "Qwen/Qwen2-0.5B-Instruct",
"architectures": [
"Qwen2ForCausalLM"
],
"attention_dropout": 0.0,
"bos_token_id": 151643,
"eos_token_id": 151645,
"hidden_act": "silu",
"hidden_size": 896,
"initializer_range": 0.02,
"intermediate_size": 4864,
"max_position_embeddings": 32768,
"max_window_layers": 24,
"model_type": "qwen2",
"num_attention_heads": 14,
"num_hidden_layers": 24,
"num_key_value_heads": 2,
"rms_norm_eps": 1e-06,
"rope_theta": 1000000.0,
"sliding_window": 32768,
"tie_word_embeddings": true,
"torch_dtype": "bfloat16",
"transformers_version": "4.41.2",
"use_cache": true,
"use_sliding_window": false,
"vocab_size": 151936
}
_name_or_path
:- 作用:指定了模型的名称或路径。这里它指向了一个预训练模型的标识符
"Qwen/Qwen2-0.5B-Instruct"
,这通常用于加载预训练的模型权重。
- 作用:指定了模型的名称或路径。这里它指向了一个预训练模型的标识符
model_type
:- 作用:指明了模型的类型,这里是
"qwen2"
,这意味着该配置是为一个名为qwen2
的特定模型架构设计的。这可能是一个自定义的或研究性质的模型,不是广泛认知的标准模型类型(如BERT、GPT等)。
- 作用:指明了模型的类型,这里是
hidden_size
:- 作用:定义了模型中隐藏层的大小,这里是896。这个参数影响了模型处理信息的容量和复杂度。
num_hidden_layers
:- 作用:指定了模型中的隐藏层数量,这里是24层。这个参数决定了模型的深度,层数越多,模型能够学习到的特征就越复杂。
num_attention_heads
:- 作用:定义了多头注意力机制中“头”的数量,这里是14个。多头注意力是Transformer模型的核心组件之一,它允许模型同时关注输入序列的不同部分。
max_position_embeddings
:- 作用:指定了模型能够处理的最大序列长度,这里是32768。这意味着模型可以处理的文本或数据序列最长为32768个标记。
vocab_size
:- 作用:表示模型词汇表的大小,这里是151936。词汇表是模型能够理解的单词和标记的集合,
vocab_size
就是这个集合的大小。
- 作用:表示模型词汇表的大小,这里是151936。词汇表是模型能够理解的单词和标记的集合,
bos_token_id
和eos_token_id
:- 作用:分别指定了文本序列的开始(BOS, Beginning of Sequence)和结束(EOS, End of Sequence)标记的ID。在文本生成或处理任务中,这些特殊标记用于指示序列的开始和结束。
torch_dtype
:- 作用:指定了模型训练或推理时使用的数据类型,这里是
bfloat16
。bfloat16
是一种浮点数格式,旨在在保持数值精度的同时减少内存使用和计算成本,特别适用于深度学习模型。
- 作用:指定了模型训练或推理时使用的数据类型,这里是
transformers_version
:- 作用:指明了该配置所依赖的
transformers
库的版本,这里是4.41.2
。这对于确保配置与库版本之间的兼容性非常重要。
- 作用:指明了该配置所依赖的
(3)generation_config.json
{
"bos_token_id": 151643,
"do_sample": true,
"eos_token_id": [
151645,
151643
],
"pad_token_id": 151643,
"repetition_penalty": 1.1,
"temperature": 0.7,
"top_k": 20,
"top_p": 0.8,
"transformers_version": "4.41.2"
}
bos_token_id
:- 作用:指定了文本生成开始时的特殊标记(Beginning of Sequence)的ID。在生成文本时,这个标记会被添加到序列的开始位置。
- 值:
151643
,表示在词汇表中对应的特殊标记的ID。
do_sample
:- 作用:指示是否使用采样方法来生成文本。如果为
true
,则根据概率分布进行采样;如果为false
,则可能使用贪心搜索或其他确定性方法。 - 值:
true
,表示使用采样方法。
- 作用:指示是否使用采样方法来生成文本。如果为
eos_token_id
:- 作用:指定了文本生成结束时的特殊标记(End of Sequence)的ID列表。当生成过程中遇到这些ID之一时,文本生成将停止。
- 值:
[151645, 151643]
,表示在词汇表中对应的两个特殊标记的ID,其中151643
也用作开始标记,但在某些情况下也可能用作结束标记,具体取决于模型训练时的约定。
pad_token_id
:- 作用:指定了用于填充序列以达到固定长度的特殊标记的ID。在批处理中,为了保持序列长度的一致性,较短的序列会被填充到这个长度。
- 值:
151643
,表示在词汇表中对应的特殊标记的ID。这里有点不寻常,因为通常开始和结束标记会有不同的ID,但在这个配置中,它们共享了同一个ID。
repetition_penalty
:- 作用:用于控制生成文本中重复单词的惩罚力度。较高的值会惩罚重复,有助于生成更多样化的文本。
- 值:
1.1
,表示对重复单词施加轻微的惩罚。
temperature
:- 作用:控制生成文本时的随机性。较高的温度值会增加生成文本的多样性,但可能会降低文本的质量;较低的温度值会使生成更加保守,但可能会增加文本的连贯性。
- 值:
0.7
,表示一个适中的温度值,旨在在多样性和质量之间取得平衡。
top_k
:- 作用:在采样过程中,仅考虑概率最高的
k
个候选词。这有助于减少生成文本中的噪声和不合理词汇。 - 值:
20
,表示在采样时仅考虑概率最高的20个候选词。
- 作用:在采样过程中,仅考虑概率最高的
top_p
:- 作用:与
top_k
类似,但它是基于累积概率的。top_p
会保留累积概率最高的p
比例内的候选词。这有助于在保持多样性的同时,进一步减少不合理词汇的生成。 - 值:
0.8
,表示保留累积概率最高的80%内的候选词。
- 作用:与
transformers_version
:- 作用:指示生成配置所依赖的
transformers
库的版本。这对于确保配置与库版本之间的兼容性至关重要。 - 值:
4.41.2
,表示该配置是为transformers
库的4.41.2版本设计的。
- 作用:指示生成配置所依赖的
三.py311文件夹
py311
文件夹的作用是作为Python 3.11版本的环境或安装的根目录,它包含了运行Python 3.11版本所需的所有关键文件和目录。
四.local_dataset.py
1.源码
import json
from datasets import Dataset
custom_prompt = """下面列出了一个问题. 请写出问题的答案.
### 问题:
{}
### 答案:
{}"""
class LocalJsonDataset:
def __init__(self, json_file, tokenizer, max_seq_length=2048):
self.json_file = json_file
self.tokenizer = tokenizer
self.max_seq_length = max_seq_length
self.dataset = self.load_dataset()
def load_dataset(self):
with open(self.json_file, 'r', encoding='utf-8') as f:
data = json.load(f)
texts = []
for item in data:
text = custom_prompt.format(item['question'], item['answer']) + self.tokenizer.eos_token
texts.append(text)
dataset_dict = {
'text': texts # 添加'text'字段以适配SFTTrainer
}
dataset = Dataset.from_dict(dataset_dict)
return dataset
def get_dataset(self):
return self.dataset
2.解析:
1.总体概述
这段代码定义了一个名为 LocalJsonDataset
的类,其主要作用是从一个本地的 JSON 文件中加载问答对数据,并将这些数据转换成适合自然语言处理(NLP)任务(特别是那些需要序列到序列(seq2seq)模型处理的任务,如问答系统)的数据集格式。
2.函数分析
-
初始化 (
__init__
方法):def __init__(self, json_file, tokenizer, max_seq_length=2048): self.json_file = json_file self.tokenizer = tokenizer self.max_seq_length = max_seq_length self.dataset = self.load_dataset()
接收一个 JSON 文件路径
json_file
、一个分词器(tokenizer)对象tokenizer
(用于将文本转换为模型可以理解的格式),以及一个最大序列长度max_seq_length
(尽管在这个实现中,max_seq_length
参数被接收了,但在后续处理中并未直接使用,这可能是因为后续处理(如编码文本)在更高级别的处理(如模型训练)中进行)。 -
加载数据集 (
load_dataset
方法):- 打开JSON文件并加载数据:
with open(self.json_file, 'r', encoding='utf-8') as f: data = json.load(f)
- 这段代码使用
with
语句和open
函数以只读模式('r'
)和UTF-8编码(encoding='utf-8'
)打开一个名为self.json_file
的JSON文件。self.json_file
是类的一个属性,预期在类的其他部分或实例化时已被正确设置。 - 使用
json.load(f)
从文件中加载JSON数据,并将其存储在变量data
中。这里假设data
是一个列表,列表中的每个元素都是一个字典,包含了question
和answer
等键。
- 这段代码使用
- 初始化文本列表:
texts = []
- 初始化一个空列表
texts
,用于存储处理后的文本数据。
- 初始化一个空列表
- 遍历数据并构建文本:
- 这段代码遍历
data
列表中的每个元素(每个元素都是一个包含question
和answer
的字典) - 使用
custom_prompt.format(item['question'], item['answer'])
将每个项目的question
和answer
插入到一个自定义的提示模板custom_prompt
中。custom_prompt
可能是一个字符串,其中包含用于格式化这些值的占位符(如{}
)。 -
本文件中: custom_prompt = """下面列出了一个问题. 请写出问题的答案. ### 问题: {} ### 答案: {}""" 注:在这个例子中,三个双引号(""")用于定义一个多行字符串(也称为三引号字符串)。 在Python中,你可以使用单引号(')、双引号(") 或三引号(''' 或 """)来定义字符串。 然而,三引号字符串特别适用于包含多行文本 或需要在字符串内部包含引号而不使用转义字符(\)的场景。 当你使用三个双引号(或三个单引号)来定义字符串时, 你可以在这个字符串内部自由地换行,而不需要在每个换行符前加上反斜杠(\)。 此外,字符串内部的引号(无论是单引号还是双引号)都可以直接书写, 而不需要转义,这使得在定义包含引号文本的字符串时更加方便。 在这个示例中,custom_prompt 是一个多行字符串,它使用了两个 {} 作为占位符。当 custom_prompt.format(item['question'], item['answer']) 被调用时,第一个 {} 会被 item['question'] 的值替换,第二个 {} 会被 item['answer'] 的值替换。
例如,如果
data
列表中的一个元素是这样的:{"question": "世界上有多少个国家?", "answer": "根据联合国的数据,世界上有大约195个国家。"}
输出将会是:
下面列出了一个问题. 请写出问题的答案. ### 问题: 世界上有多少个国家? ### 答案: 根据联合国的数据,世界上有大约195个国家。
- 将格式化后的文本字符串与分词器(Tokenizer)的结束符(
self.tokenizer.eos_token
)连接起来,然后将其添加到texts
列表中。结束符是文本生成任务中常用的,用于指示生成的文本结束。
- 这段代码遍历
- 构建Dataset对象:
dataset_dict = { 'text': texts # 添加'text'字段以适配SFTTrainer } dataset = Dataset.from_dict(dataset_dict)
- 创建一个字典
dataset_dict
,其中包含一个键'text'
,其值为之前构建的texts
列表。这个结构是为了与Dataset.from_dict
方法的输入要求相匹配。 - 使用
Dataset.from_dict(dataset_dict)
方法将dataset_dict
字典转换成一个Dataset
对象。Dataset
是许多NLP库(如Hugging Face的datasets
库)中用于表示数据集的标准类。这里的Dataset
对象应该被设计为与后续的数据处理或模型训练步骤兼容。
- 创建一个字典
- 返回Dataset对象:
return dataset
- 最后,函数返回构建好的
Dataset
对象,以便在其他地方使用,比如作为模型训练的输入。总结来说,load_dataset
函数从一个JSON文件中加载问答数据,将这些数据格式化成特定的文本格式,并将这些文本包装成一个Dataset
对象以供后续使用。
- 最后,函数返回构建好的
- 打开JSON文件并加载数据:
-
获取数据集 (
get_dataset
方法): 提供一个接口来访问已加载并转换为Dataset
对象的数据集。
总之,这段代码的主要作用是将存储在本地 JSON 文件中的问答对数据转换成适合训练 NLP 模型(特别是那些需要处理文本序列的模型)的格式。通过将问题和答案格式化为包含特定模板的字符串,并将这些字符串作为单独的文本序列存储在 Dataset
对象中,这个类为后续的模型训练或评估过程提供了便利。
五.run.py
1.源码
from unsloth import FastLanguageModel
max_seq_length = 2048
dtype = None
load_in_4bit = False
model, tokenizer = FastLanguageModel.from_pretrained(
model_name="lora_model",
max_seq_length=max_seq_length,
dtype=dtype,
load_in_4bit=load_in_4bit,
)
FastLanguageModel.for_inference(model)
def generate_answer(question):
input_text = f"下面列出了一个问题. 请写出问题的答案.\n####问题:{question}\n####答案:"
inputs = tokenizer(
[input_text],
return_tensors="pt",
padding=True,
truncation=True
).to("cuda")
outputs = model.generate(**inputs, max_new_tokens=2048, use_cache=True)
decoded_output = tokenizer.batch_decode(outputs, skip_special_tokens=True)[0]
return decoded_output.split('<|im_end|>')[0].strip()
print("请输入您的问题,输入'exit'退出:")
while True:
user_input = input("> ")
if user_input.lower() == 'exit':
print("程序已退出。")
break
answer = generate_answer(user_input)
print("---")
print(answer)
2.解析
1.总体概述
这段代码实现了一个问答系统,通过FastLanguageModel.from_pretrained
方法加载一个预训练的模型(这里模型名为"lora_model"
)和对应的分词器。,用户可以输入问题,系统会生成相应的答案。主要步骤包括设置模型参数、编码用户输入、生成答案并输出。整体上,它允许用户与模型进行交互,直到用户选择退出。
2.代码分析
from unsloth import FastLanguageModel
- 这行代码从
unsloth
这个Python库中导入了FastLanguageModel
类
max_seq_length = 2048
dtype = None
load_in_4bit = False
- 这三行代码设置了加载模型时使用的三个参数:
max_seq_length
:模型处理文本时的最大序列长度,这里设置为2048。dtype
:模型数据类型,这里未指定(None
),通常意味着使用默认的浮点数类型(如torch.float32
)。load_in_4bit
:是否以4位精度加载模型,这里设置为False
,表示不使用4位量化来节省内存和加速加载。
model, tokenizer = FastLanguageModel.from_pretrained(
model_name="lora_model",
max_seq_length=max_seq_length,
dtype=dtype,
load_in_4bit=load_in_4bit,
)
- 这行代码使用
FastLanguageModel.from_pretrained
方法加载了一个预训练的语言模型和对应的分词器。model_name="lora_model"
指定了要加载的模型名称,这里lora_model
是一个使用LoRA(Low-Rank Adaptation)技术微调过的模型。其他参数如max_seq_length
、dtype
和load_in_4bit
用于控制加载过程的细节。
FastLanguageModel.for_inference(model)
- 将模型设置为推理模式,以优化性能。
def generate_answer(question):
...
- 定义了一个函数
generate_answer
,它接收一个问题作为输入,并返回模型生成的答案。
input_text = f"下面列出了一个问题. 请写出问题的答案.\n####问题:{question}\n####答案:"
- 在函数内部,首先构造了一个包含问题和占位符答案的输入文本。这个文本被设计为引导模型生成问题的答案。
inputs = tokenizer(
[input_text],
return_tensors="pt",
padding=True,
truncation=True
).to("cuda")
- 使用分词器处理输入文本,将其转换为模型可以理解的格式(张量),并设置返回的数据类型为PyTorch张量(
"pt"
)。然后,通过.to("cuda")
将张量移动到CUDA设备上(如果可用),以加速计算。
outputs = model.generate(**inputs, max_new_tokens=2048, use_cache=True)
- 调用模型的
generate
方法来生成文本。**inputs
将输入张量的字典解包为generate
方法的参数。max_new_tokens=2048
限制了生成的新令牌数量,use_cache=True
允许模型在生成过程中重用之前的隐藏状态,以节省计算。
decoded_output = tokenizer.batch_decode(outputs, skip_special_tokens=True)[0]
- 使用分词器的
batch_decode
方法将生成的令牌ID解码回文本。skip_special_tokens=True
表示在解码时跳过特殊令牌(如填充令牌或模型特有的特殊令牌)。由于这里只处理了一个输入,所以通过索引[0]
获取解码后的文本。
return decoded_output.split('<|im_end|>')[0].strip()
- 假设生成的文本以
<|im_end|>
作为结束标记,这行代码通过分割字符串并取第一个元素来移除结束标记之后的所有内容。然后,使用.strip()
方法去除字符串两端的空白字符,并返回处理后的答案。
print("请输入您的问题,输入'exit'退出:") py
while True:
...
- 这部分代码设置了一个用户交互循环,提示用户输入问题,并处理用户的输入,直到用户输入
exit
来退出程序。
六.run.sh
1.源码
./py311/bin/python3 run.py
2.解析
这个run.sh
脚本的作用是使用位于./py311/bin/
目录下的python3
解释器来执行当前目录下(或指定路径下的,如果run.py
路径在命令中明确给出)的run.py
Python 脚本文件。
具体来说,当你在命令行(或终端)中运行./run.sh
(假设run.sh
文件具有执行权限,如果没有,你需要先通过chmod +x run.sh
命令来赋予它执行权限)时,会发生以下步骤:
-
脚本首先会定位到
./py311/bin/
目录,这个目录通常是一个特定Python版本(在这个例子中是Python 3.11,但请注意实际版本可能有所不同,这里只是基于目录名推测)的虚拟环境(virtualenv)或conda环境的bin目录。这个目录包含了该Python环境的解释器(python3)以及其他一些工具(如pip等)。 -
接着,它使用找到的
python3
解释器来执行run.py
文件。这意味着run.py
将在这个特定的Python环境中运行,该环境可能包含了一些特定的库(packages)或模块(modules),这些库或模块可能是全局Python环境中不存在的,或者其版本与全局环境中的不同。
这种做法在Python开发中很常见,因为它允许开发者为每个项目创建一个隔离的Python环境,从而避免不同项目之间因依赖冲突而导致的问题。
简而言之,./py311/bin/python3 run.py
这条命令通过脚本run.sh
自动执行,目的是在指定的Python环境中运行run.py
脚本。
七.train.sh
1.源码
./py311/bin/python3 train.py
2.解析
这条命令的作用是使用位于 ./py311/bin/
目录下的 python3
解释器来执行当前目录(或指定路径,如果 train.py
文件的路径在命令中明确给出)下的 train.py
Python 脚本文件
与上面作用类似,目的是在指定的Python环境中运行train.py
脚本。
八.train.py
1.源码
# 从unsloth库导入FastLanguageModel类
from unsloth import FastLanguageModel
# 这是一个自定义的模块,用于加载本地JSON格式的数据集
from local_dataset import LocalJsonDataset
# 从safetensors.torch导入模型加载和保存函数
from safetensors.torch import load_model, save_model
# 设置最大序列长度
max_seq_length = 2048
# 设置数据类型(这里为None,表示使用默认数据类型)
dtype = None
# 是否以4位精度加载模型(这里设置为False)
load_in_4bit = False
# 加载预训练模型和分词器
model, tokenizer = FastLanguageModel.from_pretrained(
model_name="./model/Qwen2-0.5B-Instruct",
max_seq_length=max_seq_length,
dtype=dtype,
load_in_4bit=load_in_4bit,
)
# 使用PEFT技术对模型进行微调
model = FastLanguageModel.get_peft_model(
model, # 基础模型
r=16, # LoRA的秩
# 选择要应用LoRA的模块
target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],
lora_alpha=16, # LoRA的缩放因子
lora_dropout=0, # LoRA的dropout率(0为优化设置)
bias="none", # 偏置项设置('none'为优化设置)
use_gradient_checkpointing="unsloth", # 使用梯度检查点以减少VRAM使用
random_state=3407, # 随机种子
use_rslora=False, # 是否使用排名稳定的LoRA
loftq_config=None, # LoftQ配置(这里不使用)
)
# 加载自定义数据集
custom_dataset = LocalJsonDataset(json_file='train_data.json', tokenizer=tokenizer, max_seq_length=max_seq_length)
# 获取预处理后的数据集
dataset = custom_dataset.get_dataset()
# 设置训练配置
from trl import SFTTrainer
from transformers import TrainingArguments
from unsloth import is_bfloat16_supported
trainer = SFTTrainer(
model=model,
tokenizer=tokenizer,
train_dataset=dataset,
dataset_text_field="text",
max_seq_length=max_seq_length,
dataset_num_proc=2,
args=TrainingArguments(
per_device_train_batch_size=4,
gradient_accumulation_steps=8,
warmup_steps=20,
max_steps=2000,
learning_rate=5e-5,
fp16=not is_bfloat16_supported(),
bf16=is_bfloat16_supported(),
logging_steps=1,
optim="adamw_8bit",
weight_decay=0.01,
lr_scheduler_type="linear",
seed=3407,
output_dir="outputs",
#save_strategy="no"
),
)
# 开始训练
trainer.train()
# 保存微调后的模型和分词器
model.save_pretrained("lora_model")
tokenizer.save_pretrained("lora_model")
# 将模型配置为推理模式
FastLanguageModel.for_inference(model)
def generate_answer(question):
input_text = f"下面列出了一个问题. 请写出问题的答案.\n####问题:{question}\n####答案:"
# 对输入文本进行分词并转换为模型所需的张量格式
inputs = tokenizer(
[input_text],
return_tensors="pt",
padding=True,
truncation=True
).to("cuda")
# 使用模型生成回答
outputs = model.generate(**inputs, max_new_tokens=2048, use_cache=True)
# 解码输出并返回答案
decoded_output = tokenizer.batch_decode(outputs, skip_special_tokens=True)[0]
return decoded_output.split('<|im_end|>')[0].strip()
print("请输入您的问题,输入'exit'退出:")
while True:
user_input = input("> ")
if user_input.lower() == 'exit':
print("程序已退出。")
break
answer = generate_answer(user_input)
print("---")
print(answer)
2.解析
1.总体概述
这段代码展示了如何使用预训练的语言模型(通过LoRA进行微调)、加载自定义数据集、配置训练参数、执行训练过程、保存模型、以及将模型用于生成式任务(如问答系统)的完整流程。
2.代码分析
from unsloth import FastLanguageModel
from local_dataset import LocalJsonDataset
from safetensors.torch import load_model, save_model
- 从
unsloth
库导入FastLanguageModel
类,该类用于加载和使用快速语言模型。 - 从自定义模块
local_dataset
导入LocalJsonDataset
类,用于加载本地JSON格式的数据集。 - 从
safetensors.torch
导入load_model
和save_model
函数,这两个函数用于安全地加载和保存PyTorch模型。
max_seq_length = 2048
dtype = None
load_in_4bit = False
- 设置最大序列长度为2048。
- 设置数据类型为None,表示使用默认数据类型。
- 设置不使用4位精度加载模型。
model, tokenizer = FastLanguageModel.from_pretrained(
model_name="./model/Qwen2-0.5B-Instruct",
max_seq_length=max_seq_length,
dtype=dtype,
load_in_4bit=load_in_4bit,
)
# 使用PEFT技术对模型进行微调
model = FastLanguageModel.get_peft_model(
model, # 基础模型
r=16, # LoRA的秩
# 选择要应用LoRA的模块
target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],
lora_alpha=16, # LoRA的缩放因子
lora_dropout=0, # LoRA的dropout率(0为优化设置)
bias="none", # 偏置项设置('none'为优化设置)
use_gradient_checkpointing="unsloth", # 使用梯度检查点以减少VRAM使用
random_state=3407, # 随机种子
use_rslora=False, # 是否使用排名稳定的LoRA
loftq_config=None, # LoftQ配置(这里不使用)
)
-
r = 16,
:选择r
的值,这是一个大于0的数字,用于LoRA(Low-Rank Adaptation)方法中的秩(rank)。建议的值有8, 16, 32, 64, 128。 -
target_modules = [...]
:指定要应用LoRA的模型层或模块。在这个例子中,它包括了Transformer架构中的查询(query)、键(key)、值(value)投影层,输出(output)投影层,以及门控(gate)、上投影(up projection)、下投影(down projection)层。 -
lora_alpha = 16,
:LoRA的一个超参数,用于控制LoRA权重矩阵的缩放。 -
lora_dropout = 0,
:LoRA的dropout率。支持任何值,但设置为0时效率最高。 -
bias = "none",
:指定是否对LoRA的偏差项进行适配。支持任何值,但设置为"none"时效率最高。 -
use_gradient_checkpointing = "unsloth",
:启用梯度检查点(gradient checkpointing)以节省内存,允许处理更长的上下文。特别地,"unsloth"是一个优化选项,使用大约30%更少的VRAM,并可能支持2倍大的批量大小。 -
random_state = 3407,
:设置随机种子以确保结果的可重复性。 -
use_rslora = False,
:是否使用秩稳定的LoRA(Rank Stabilized LoRA)。设置为False表示不使用。 -
loftq_config = None,
:LoftQ(Low-rank Factorized Quantization)的配置。如果设置为None,则不使用LoftQ。LoftQ是另一种参数效率优化技术。
通过这些设置,你可以对大型语言模型进行高效的参数效率微调(PEFT),以适应不同的任务和数据集,同时减少内存消耗和提高训练效率。
加载自定义数据集
# 加载自定义数据集
custom_dataset = LocalJsonDataset(json_file='train_data.json', tokenizer=tokenizer, max_seq_length=max_seq_length)
# 获取预处理后的数据集
dataset = custom_dataset.get_dataset()
第一行代码通过创建一个LocalJsonDataset
实例来加载一个名为train_data.json
的JSON文件作为自定义数据集。
这两行代码的作用是从一个JSON文件中加载自定义数据集,并将其预处理成模型可以直接用于训练或评估的格式。这是大多数NLP项目中的常见步骤,因为原始文本数据通常需要先经过一系列的预处理步骤才能被模型有效地处理。
# 导入SFTTrainer和其他相关模块
from trl import SFTTrainer
from transformers import TrainingArguments
from unsloth import is_bfloat16_supported
trainer = SFTTrainer(
model=model,
tokenizer=tokenizer,
train_dataset=dataset,
dataset_text_field="text",
max_seq_length=max_seq_length,
dataset_num_proc=2,
args=TrainingArguments(
per_device_train_batch_size=4,
gradient_accumulation_steps=8,
warmup_steps=20,
max_steps=2000,
learning_rate=5e-5,
fp16=not is_bfloat16_supported(),
bf16=is_bfloat16_supported(),
logging_steps=1,
optim="adamw_8bit",
weight_decay=0.01,
lr_scheduler_type="linear",
seed=3407,
output_dir="outputs",
#save_strategy="no"
),
)
初始化训练器配置
model
- 作用:指定要训练的模型。这里这个模型指
./model/Qwen2-0.5B-Instruct下的模型
tokenizer
- 作用:指定用于文本数据预处理和编码的分词器(前面代码设置的)。分词器将原始文本转换为模型可以理解的输入格式(如token IDs)。
train_dataset
- 作用:指定训练数据集(前面代码获取的数据集)。
- 这通常是一个已经加载并预处理好的数据集,包含了用于训练模型的数据样本。
dataset_text_field
- 作用:指定数据集中包含文本数据的字段名(dataset_text_field="text")。这个配置项是特定于
SFTTrainer
的,用于告诉训练器如何从数据集中提取文本数据。 -
dataset_dict = { 'text': texts # 添加'text'字段以适配SFTTrainer }
上面代码在loacal_dataset.py可以看到
max_seq_length
- 作用:指定输入序列的最大长度。在训练过程中,如果文本数据的长度超过了这个值,它将被截断;如果长度小于这个值,它将被填充到指定的长度。
dataset_num_proc
- 作用:指定用于数据集预处理的进程数。这有助于并行处理数据集,加快数据加载速度。然而,这个配置项同样可能是特定于
SFTTrainer
的,而不是transformers
库的标准配置。
TrainingArguments
配置项
per_device_train_batch_size
- 作用:指定每个设备上的批量大小。在分布式训练或多GPU训练中,这个值会决定每个GPU/设备一次处理多少个样本。
gradient_accumulation_steps
- 作用:指定梯度累积的步数。这允许在更新模型参数之前累积多个小批量的梯度,从而模拟较大的批量大小,有助于稳定训练过程。
warmup_steps
- 作用:指定学习率预热步骤数。在训练初期,学习率会逐渐从0增加到设定的初始学习率,这有助于模型在训练初期更稳定地学习。
max_steps
- 作用:指定训练过程中的最大步骤数。达到这个步骤数后,训练将停止。
learning_rate
- 作用:指定初始学习率。学习率决定了模型参数更新的幅度。
fp16
和 bf16
- 作用:这两个配置项用于指定是否使用半精度浮点数(fp16)或bfloat16来加速训练。这有助于减少内存占用并可能加快训练速度,但可能会牺牲一些精度。
logging_steps
- 作用:指定记录日志的步骤数。在训练过程中,每隔这么多步骤,训练器将输出一次训练日志,包括损失、学习率等信息。
optim
- 作用:指定优化器的类型。在这里,
"adamw_8bit"
可能是一个自定义的优化器,结合了AdamW优化器和某种形式的8位量化。不过,请注意这不是transformers
库的标准优化器。
weight_decay
- 作用:指定权重衰减系数,用于防止模型过拟合。
lr_scheduler_type
- 作用:指定学习率调度器的类型。
"linear"
表示学习率将线性衰减到0。
seed
- 作用:指定随机种子,以确保实验的可重复性。
训练结果生成文件夹
output_dir
- 作用:
output_dir="outputs",
指定输出目录,用于保存训练过程中产生的模型权重、日志文件等。
save_strategy
(已注释)
- 作用(:如果未注释,这个配置项将指定保存模型的策略。
"no"
表示不自动保存模型。然而,由于它被注释掉了,所以实际上不会生效。在transformers
库中,save_strategy
可以是"no"
,"epoch"
,"steps"
等,用于控制模型保存的行为。
保存微调模型路径
# 开始训练
trainer.train()
# 保存微调后的模型和分词器
model.save_pretrained("lora_model")
tokenizer.save_pretrained("lora_model")
在使用像transformers
库这样的自然语言处理库时,save_pretrained
方法用于将模型(model
)和分词器(tokenizer
)保存到指定路径。在这个例子中,使用了"lora_model"
作为路径名来保存它们。这个名称的选择是任意的,你可以使用任何你喜欢的字符串作为文件名或路径,只要它符合你的文件系统和命名约定。 即将微调后的模型和分词器保存到lora_model文件夹中
model.save_pretrained("lora_model")
和 tokenizer.save_pretrained("lora_model")
括号里的参数 "lora_model"
代表的是保存模型(model
)和分词器(tokenizer
)的文件夹路径。在这种情况下,"lora_model"
是一个目录名,用于在当前工作目录下创建一个新的文件夹(如果它尚不存在的话),并将模型文件和分词器文件保存在该文件夹中。
当你调用 save_pretrained
方法时,它会将模型的权重、配置(config)和其他必要的文件(如分词器的词汇表)保存到指定的文件夹中。这样做的好处是,你可以轻松地在其他地方或在不同时间重新加载相同的模型配置和分词器,而无需担心它们之间的不一致性。
# 将模型配置为推理模式
FastLanguageModel.for_inference(model)
def generate_answer(question):
# 构造输入文本
input_text = f"下面列出了一个问题. 请写出问题的答案.\n####问题:{question}\n####答案:"
# 对输入文本进行分词并转换为模型所需的张量格式
inputs = tokenizer(
[input_text],
return_tensors="pt",
padding=True,
truncation=True
).to("cuda")
# 使用模型生成回答
outputs = model.generate(**inputs, max_new_tokens=2048, use_cache=True)
# 解码输出并返回答案
decoded_output = tokenizer.batch_decode(outputs, skip_special_tokens=True)[0]
return decoded_output.split('<|im_end|>')[0].strip()
# 允许用户输入问题并打印回答
print("请输入您的问题,输入'exit'退出:")
while True:
user_input = input("> ")
if user_input.lower() == 'exit':
print("程序已退出。")
break
answer = generate_answer(user_input)
print("---")
print(answer)
注意点:
如果你只需要部署微调后的模型进行推理,那么 lora_model
文件夹中的内容就足够了。如果你还需要分析训练过程或者从中断的地方恢复训练,则需要查看 outputs
文件夹中的内容。
output_dir
中的内容可能更加完整,因为它可能包含了除了模型权重之外的其他信息,如训练日志、优化器状态等。- 使用
model.save_pretrained()
和tokenizer.save_pretrained()
保存的则仅仅是模型和分词器的权重和配置,不包含训练过程的其他信息。 - 当使用
output_dir
时,训练器通常会根据其内部逻辑来组织目录结构。 - 使用
model.save_pretrained()
和tokenizer.save_pretrained()
时,指定的目录将直接包含模型和分词器的文件,这些文件通常包括权重文件(.bin
或.pth
等)、配置文件(.json
)等。 - 实际使用中,可能需要同时保存训练输出(通过
output_dir
)和模型/分词器权重(通过save_pretrained()
),以便将来能够重现训练过程或进行进一步的分析。 outputs
主要用于保存训练过程中的检查点和日志文件,通常不直接保存最终的模型权重和分词器文件。它可能会包含模型的中间权重,但最终的模型权重和分词器文件通常会在lora_model
中保存。因此,outputs
主要是临时和过程性的信息,而lora_model
保存的是最终可用的模型及分词器。总结
lora_model
文件夹保存的是经过LoRA微调后的最终模型状态(包括LoRA权重和分词器)。outputs
文件夹保存的是训练过程中的所有输出,包括中间检查点、日志文件等。