朵以服饰 网站建设,服装设计学校,手机上免费做ppt的软件,如何优化m网站【2024】Datawhale AI夏令营 Task3笔记——Baseline2部分代码解读及初步上分思路 
本文对可完成赛事“逻辑推理赛道#xff1a;复杂推理能力评估”初赛的Baseline2部分关键代码进行详细解读#xff0c;介绍Baseline2涉及的关键技术和初步上分思路。 
Baseline2代码由Datawhal…【2024】Datawhale AI夏令营 Task3笔记——Baseline2部分代码解读及初步上分思路 
本文对可完成赛事“逻辑推理赛道复杂推理能力评估”初赛的Baseline2部分关键代码进行详细解读介绍Baseline2涉及的关键技术和初步上分思路。 
Baseline2代码由Datawhale AI夏令营提供核心内容是将大语言模型部署至本地并在此基础上使用Lora技术微调大语言模型。另外为了加快本地推理的速度本次任务代码使用vllm技术加速大语言模型推理Task1和Task2方法的核心是直接调用外部大语言模型的API进行推理。 
注意 
1、在进行代码解读时为了简化代码、不影响代码理解本文对baseline2代码顺序进行了一定调整。 
2、运行Baseline2代码有部分注意点分别是尽量使用cuda12.1的设备一、根据选择的微调数据集修改答案提取函数五。 
3、使用ana.json作为微调数据集效果由于使用an.json。关于这两个文件的区别详见后文。 
4、本文另外尝试在不使用vLLM的情况下进行推理加速前单个问题的推理速度约为5分钟/题加速后单个问题的推理速度约为0.35分钟/题加速效果显著。 
5、本文另外发现了vLLM服务启动后GPU占用较高的问题vLLM服务启动后整体推理速度也仍有待提高目前推理所有问题在V100-32G上需要7小时。改进方式可参考使用vLLM加速大语言模型推理。 
✨Baseline2涉及的关键技术 
1、Lora微调上分 
LoRALow-Rank Adaptation微调是一种高效的模型微调技术特别适用于大型预训练语言模型的适应性调整。LoRA的核心思想是通过引入低秩矩阵来调整模型的权重从而在不显著增加模型参数数量的情况下实现对模型的微调。 
LoRA优势 可以针对不同的下游任务构建小型 LoRA 模块从而在共享预训练模型参数基础上有效地切换下游任务。  LoRA 使用自适应优化器Adaptive Optimizer不需要计算梯度或维护大多数参数的优化器状态训练更有效、硬件门槛更低。  LoRA 使用简单的线性设计在部署时将可训练矩阵与冻结权重合并不存在推理延迟。  LoRA 与其他方法正交可以组合。  
实际使用方法可参考本文“Baseline2部分代码解读”中的第二、三章节。 
2、vLLM加速推理 
vLLMVirtual Large Language Model是一个由伯克利大学LMSYS组织开源的大规模语言模型高速推理框架设计目标是在实时应用场景中大幅提升语言模型服务的吞吐量和内存使用效率。vLLM的特点包括易于使用、与Hugging Face等流行工具无缝集成以及高效的性能。 
实际使用方法可参考本文“Baseline2部分代码解读”中的第四章节。 
3、多路投票上分 
Datawhale教程中对于多路投票/多路召回的解释 所谓的“多路召回策略”就是指采用不同的策略、特征或者简单模型分别召回一部分候选集然后再把这些候选集混合在一起后供后续排序模型使用的策略。 在实践中“不同的策略、特征或者简单模型”就是指不同次调用微调后的大语言模型“召回一部分候选集”就是指分别得到不同的回答/答案选项可能不同也可能相同“把这些候选集混合在一起后供后续排序模型使用”就是指统计这些回答中每个出现过的回答的次数按照出现次数进行排序最终决定出现次数最多的回答即为答案。 
实际使用方法可参考本文“Baseline2部分代码解读”中的第五章节。 
Baseline2部分代码解读 
Baseline2的代码有一部分与Baseline1的代码相似在此文章只记录Baseline2中新出现的技术与代码内容。 
一、环境配置与数据文件准备 
1.1 环境配置 
Datawhale教程建议使用的环境配置如下 
unbuntu22.04
cuda12.1.0
py3.10
torch2.1.2我认为cuda的版本比较关键在自己配置环境的过程中一开始先选择了一台pytorch版本也是2.1.2但cuda版本是11.8的GPU进行配置最后到了启动vllm的环节时会无法启动报错信息可见本章的补充部分。 
其余依赖的python第三方包 
pip install modelscope1.9.5
pip install transformers4.39.0
pip install streamlit1.24.0
pip install sentencepiece0.1.99
pip install transformers_stream_generator0.0.4
pip install datasets2.18.0
pip install peft0.10.0
pip install openai1.17.1
pip install tqdm4.64.1
pip install transformers4.39.3
python -m pip install setuptools69.5.1
pip install vllm0.4.0.post1
pip install nest-asyncio
pip install accelerate
pip install tf-keras1.2 数据文件准备 
数据文件用于微调大语言模型使之推理能力增强。本次任务使用赛事官方提供的训练集文件round1_train_data.jsonl作为微调数据。Datawhale教程对round1_train_data.jsonl文件中的数据进行了两种处理分别得到an.json和ana.json文件。本次任务将使用an.json或ana.json文件作为微调数据集。 处理一将每个问题中的子问题和答案构建成问答对得到的结果示例  {instruction: 你是一个逻辑推理专家擅长解决逻辑推理问题。以下是一个逻辑推理的题目形式为单项选择题。所有的问题都是close-world assumption闭世界假设即未观测事实都为假。请逐步分析问题并在最后一行输出答案最后一行的格式为\答案是A\。题目如下\n\n### 题目:\n假设您需要构建一个二叉搜索树其中每个节点或者是一个空的节点称为\空节点\或者是一个包含一个整数值和两个子树的节点称为\数值节点\。以下是构建这棵树的规则\n\n1. 树中不存在重复的元素。\n2. 对于每个数值节点其左子树的所有值都小于该节点的值其右子树的所有值都大于该节点的值。\n3. 插入一个新值到一个\空节点\时该\空节点\会被一个包含新值的新的数值节点取代。\n4. 插入一个已存在的数值将不会改变树。\n\n请基于以上规则回答以下选择题\n\n### 问题:\n选择题 1\n给定一个空的二叉搜索树插入下列数字: [5, 9, 2, 10, 11, 3]下面哪个选项正确描述了结果树的结构\nA. tree(5, tree(2, tree(3, nil, nil), nil), tree(9, tree(10, nil, nil), tree(11, nil, nil)))\nB. tree(5, tree(2, nil, tree(3, nil, nil)), tree(9, nil, tree(10, nil, tree(11, nil, nil))))\nC. tree(5, tree(3, tree(2, nil, nil), nil), tree(9, nil, tree(10, tree(11, nil, nil), nil)))\nD. tree(5, nil, tree(2, nil, tree(3, nil, nil)), tree(9, tree(11, nil, nil), tree(10, nil, nil)))\n,input: ,output: B
},整个文件处理完成后数据存储在an.json文件中。  处理二通过qwen2-72b模型在train数据集上跑得正确结果并分析将每个问题中的子问题、分析及答案构建成问答对得到的结果示例 {instruction: 你是一个逻辑推理专家擅长解决逻辑推理问题。以下是一个逻辑推理的题目形式为单项选择题。所有的问题都是close-world assumption闭世界假设即未观测事实都为假。请逐步分析问题并在最后一行输出答案最后一行的格式为\答案是A\。题目如下\n\n### 题目:\n假设您需要构建一个二叉搜索树其中每个节点或者是一个空的节点称为\空节点\或者是一个包含一个整数值和两个子树的节点称为\数值节点\。以下是构建这棵树的规则\n\n1. 树中不存在重复的元素。\n2. 对于每个数值节点其左子树的所有值都小于该节点的值其右子树的所有值都大于该节点的值。\n3. 插入一个新值到一个\空节点\时该\空节点\会被一个包含新值的新的数值节点取代。\n4. 插入一个已存在的数值将不会改变树。\n\n请基于以上规则回答以下选择题\n\n### 问题:\n选择题 1\n给定一个空的二叉搜索树插入下列数字: [5, 9, 2, 10, 11, 3]下面哪个选项正确描述了结果树的结构\nA. tree(5, tree(2, tree(3, nil, nil), nil), tree(9, tree(10, nil, nil), tree(11, nil, nil)))\nB. tree(5, tree(2, nil, tree(3, nil, nil)), tree(9, nil, tree(10, nil, tree(11, nil, nil))))\nC. tree(5, tree(3, tree(2, nil, nil), nil), tree(9, nil, tree(10, tree(11, nil, nil), nil)))\nD. tree(5, nil, tree(2, nil, tree(3, nil, nil)), tree(9, tree(11, nil, nil), tree(10, nil, nil)))\n,input: ,output: 根据题目中的规则我们依次插入数字 [5, 9, 2, 10, 11, 3] 来构建二叉搜索树。\n\n首先插入数字 5树为\n\ntree(5, nil, nil)\n\n\n然后插入数字 9由于 9  5因此插入到右子树\n\ntree(5, nil, tree(9, nil, nil))\n\n\n接下来插入数字 2由于 2  5因此插入到左子树\n\ntree(5, tree(2, nil, nil), tree(9, nil, nil))\n\n\n再插入数字 10由于 10  5 并且 10  9所以插入到 9 的右子树\n\ntree(5, tree(2, nil, nil), tree(9, nil, tree(10, nil, nil)))\n\n\n继续插入数字 11由于 11  5 并且 11  9 和 11  10所以插入到 10 的右子树\n\ntree(5, tree(2, nil, nil), tree(9, nil, tree(10, nil, tree(11, nil, nil))))\n\n\n最后插入数字 3由于 3  5 但是 3  2所以插入到 2 的右子树\n\ntree(5, tree(2, nil, tree(3, nil, nil)), tree(9, nil, tree(10, nil, tree(11, nil, nil))))\n\n\n比较这个最终结果与给定的选项可以看到选项 B 正确地描述了结果树的结构。\n\n答案是B},整个文件处理完成后数据存储在ana.json文件中。  
数据文件由Datawhale处理后提供可在链接Datawhale2024年第二届世界科学智能大赛逻辑推理赛道复杂推理能力评估baseline2中查看链接中除了数据文件还有相关的代码本文分析的代码源自此链接中代码。  
可以使用如下命令将数据文件下载到服务器中 
git clone https://www.modelscope.cn/datasets/Datawhale/DW2024_Complex_reasoning_ability_assessment_qwen2-7b-lora.git这里多说一句微调数据集的质量对微调后模型推理的性能有较大的影响。根据我自己的实验结果使用an.json中数据进行微调在比赛中的分数低于使用ana.json中数据进行微调。 
1.3 大语言模型文件下载 
下载魔搭社区的Qwen2-7B-Instruct模型文件。将模型下载到服务器后我们可以直接使用模型文件推理不考虑推理速度问题的话不必再调用外部的大语言模型API。 
使用如下命令进行下载 
import torch
from modelscope import snapshot_download, AutoModel, AutoTokenizer
import os
model_dir  snapshot_download(qwen/Qwen2-7B-Instruct, cache_dir./, revisionmaster)补充使用cuda11.8的GPU配置服务无法启动vllm 
在安装完需要使用的python第三方包、微调完成后在启动vllm时出现如下多则报错信息最终未能解决此处仅将尝试解决报错的过程进行记录。 
报错信息1 
ImportError: libcudart.so.12: cannot open shared object file: No such file or directoryvllm的github仓库有人就这一问题进行提问ImportError: libcudart.so.12得到的回复是  
这个回复的意思是要么使用cuda 12.1的环境要么安装适合cuda 11.8的vllm。 
在xformers的github仓库中安装cuda 11.8对应的vllm使用的指令如下前提是需要已有pytorch 2.3.1这与教程推荐的pytorch版本又有出入但是我忘记当时自己是不是没看到这个前提、仍然在pytorch2.1.2的情况下执行了这条命令安装成功了但是后续仍然有新的报错。 
# cuda 11.8 version
pip3 install -U xformers --index-url https://download.pytorch.org/whl/cu118报错信息2 
xFormers cant load C/CUDA extensions.针对这则报错信息Stackflow上的一位网友建议检查xFormers与pytorch的版本是否对应。可在此网站上查看xFormers和pytorch的版本对应信息https://anaconda.org/xformers/xformers/files。在这个网页上看到与pytorch2.1.2、cuda11.8对应的xformers包是linux-64/xformers-0.0.23.post1-py310_cu11.8.0_pyt2.1.2.tar.bz2使用wget命令将这个包下载到服务器后安装 
wget https://anaconda.org/xformers/xformers/0.0.23.post1/download/linux-64/xformers-0.0.23.post1-py310_cu11.8.0_pyt2.1.2.tar.bz2
pip install xformers-0.0.23.post1-py310_cu11.8.0_pyt2.1.2.tar.bz2参考BUG:C/CUDA extensions. xFormers was built for: PyTorch 2.1.2cu121 with CUDA 1201 (you have 2.1.0 
报错信息3 
RuntimeError: CUDA error: no kernel image is available for execution on the device文章《解决CUDA error: no kernel image is available for execution on the device》的作者认为此报错由GPU算力与pytorch依赖的CUDA的算力不匹配造成建议重新安装pytorch故使用如下命令重新安装pytorch 
pip install --force-reinstall torch2.1.2 torchvision0.16.2 torchaudio2.1.2 --index-url https://download.pytorch.org/whl/cu118然而这并没有帮助我解决这一报错。由于时间已晚不再深入debug使用新的cuda版本为2.1.2的GPU服务器配置环境。 
二、Lora微调 
2.1 加载微调所需环境及数据文件 
from datasets import Dataset
import pandas as pd
from transformers import AutoTokenizer, AutoModelForCausalLM, DataCollatorForSeq2Seq, TrainingArguments, Trainer, GenerationConfig
import torch
from peft import LoraConfig, TaskType, get_peft_modeldf  pd.read_json(an.json) # 这里先使用an.json文件
ds  Dataset.from_pandas(df) # 将JSON文件转换为CSV文件2.2 加载Tokenizer及预训练模型 
pretrained_model_path  ./qwen/Qwen2-7B-Instruct # 具体路径需按实际情况修改# 加载Tokenizer
tokenizer  AutoTokenizer.from_pretrained(pretrained_model_path, use_fastFalse, trust_remote_codeTrue)# 加载预训练模型
model  AutoModelForCausalLM.from_pretrained(pretrained_model_path, device_mapauto,torch_dtypetorch.bfloat16)2.3 定义数据格式化函数并检查 
使用Lora微调大语言模型时训练数据需要经过格式化、编码即tokenize操作然后再输入模型进行微调训练。这个过程一般包括 
将输入文本编码为 input_ids将输出文本编码为 labels。 
编码之后的结果都是多维的向量。 
定义预处理函数process_func对每一个样本的输入、输出文本进行编码返回编码后的字典。 
def process_func(example):MAX_LENGTH  1800 # Llama分词器会将一个中文字切分为多个token因此需要放开一些最大长度保证数据的完整性input_ids, attention_mask, labels  [], [], []# 对输入部分进行分词instruction  tokenizer(f|im_start|system\n你是一个逻辑推理专家擅长解决逻辑推理问题。|im_end|\n|im_start|user\n{example[instruction]  example[input]}|im_end|\n|im_start|assistant\n, add_special_tokensFalse) # add_special_tokensFalse表示不在开头加special_tokens# 对输出部分进行分词response  tokenizer(f{example[output]}, add_special_tokensFalse)input_ids  instruction[input_ids]  response[input_ids]  [tokenizer.pad_token_id] # input_ids是指token在词典中对应的序号attention_mask  instruction[attention_mask]  response[attention_mask]  [1] # 因为eos token也要关注所以 补充为1labels  [-100] * len(instruction[input_ids])  response[input_ids]  [tokenizer.pad_token_id] # 对于instruction部分的 token用-100填充通常用于忽略计算损失response部分的token则直接用其token IDs并在末尾添加一个pad_token_id。if len(input_ids)  MAX_LENGTH: # 做截断只保留最靠后的部分input_ids  input_ids[:MAX_LENGTH]attention_mask  attention_mask[:MAX_LENGTH]labels  labels[:MAX_LENGTH]return {input_ids: input_ids,attention_mask: attention_mask,labels: labels}调用process_func函数处理数据。 
tokenized_id  ds.map(process_func, remove_columnsds.column_names)调用process_func函数处理数据后得到的数据集结构如下 
Dataset({features: [input_ids, attention_mask, labels],num_rows: 1204
})进行decode数据检查选择数据集中第一个问答对检查其输入文本的解码情况选择数据集中第二个问答对检查其输出文本的解码情况。 
tokenizer.decode(tokenized_id[0][input_ids]) # 检查输入文本的解码结果
tokenizer.decode(list(filter(lambda x: x ! -100, tokenized_id[1][labels]))) # 检查输出文本的解码结果输入文本的解码结果如下 
|im_start|system\n你是一个逻辑推理专家擅长解决逻辑推理问题。|im_end|\n|im_start|user\n你是一个逻辑推理专家擅长解决逻辑推理问题。以下是一个逻辑推理的题目形式为单项选择题。所有的问题都是close-world assumption闭世界假设即未观测事实都为假。请逐步分析问题并在最后一行输出答案最后一行的格式为答案是A。题目如下\n\n### 题目:\n假设您需要构建一个二叉搜索树其中每个节点或者是一个空的节点称为空节点或者是一个包含一个整数值和两个子树的节点称为数值节点。以下是构建这棵树的规则\n\n1. 树中不存在重复的元素。\n2. 对于每个数值节点其左子树的所有值都小于该节点的值其右子树的所有值都大于该节点的值。\n3. 插入一个新值到一个空节点时该空节点会被一个包含新值的新的数值节点取代。\n4. 插入一个已存在的数值将不会改变树。\n\n请基于以上规则回答以下选择题\n\n### 问题:\n选择题 1\n给定一个空的二叉搜索树插入下列数字: [5, 9, 2, 10, 11, 3]下面哪个选项正确描述了结果树的结构\nA. tree(5, tree(2, tree(3, nil, nil), nil), tree(9, tree(10, nil, nil), tree(11, nil, nil)))\nB. tree(5, tree(2, nil, tree(3, nil, nil)), tree(9, nil, tree(10, nil, tree(11, nil, nil))))\nC. tree(5, tree(3, tree(2, nil, nil), nil), tree(9, nil, tree(10, tree(11, nil, nil), nil)))\nD. tree(5, nil, tree(2, nil, tree(3, nil, nil)), tree(9, tree(11, nil, nil), tree(10, nil, nil)))\n|im_end|\n|im_start|assistant\n根据题目中的规则我们依次插入数字 [5, 9, 2, 10, 11, 3] 来构建二叉搜索树。\n\n首先插入数字 5树为\n\ntree(5, nil, nil)\n\n\n然后插入数字 9由于 9  5因此插入到右子树\n\ntree(5, nil, tree(9, nil, nil))\n\n\n接下来插入数字 2由于 2  5因此插入到左子树\n\ntree(5, tree(2, nil, nil), tree(9, nil, nil))\n\n\n再插入数字 10由于 10  5 并且 10  9所以插入到 9 的右子树\n\ntree(5, tree(2, nil, nil), tree(9, nil, tree(10, nil, nil)))\n\n\n继续插入数字 11由于 11  5 并且 11  9 和 11  10所以插入到 10 的右子树\n\ntree(5, tree(2, nil, nil), tree(9, nil, tree(10, nil, tree(11, nil, nil))))\n\n\n最后插入数字 3由于 3  5 但是 3  2所以插入到 2 的右子树\n\ntree(5, tree(2, nil, tree(3, nil, nil)), tree(9, nil, tree(10, nil, tree(11, nil, nil))))\n\n\n比较这个最终结果与给定的选项可以看到选项 B 正确地描述了结果树的结构。\n\n答案是B|endoftext|输出文本的解码结果如下 
根据题目描述的函数逻辑我们来逐步分析输入列表 [3,7,5,9] 的处理过程\n\n1. 列表非空开始处理第一个元素3。\n2. 3是奇数根据规则应被删除。\n3. 处理下一个元素7同样为奇数删除。\n4. 继续处理5同样是奇数删除。\n5. 最后处理9依然是奇数删除。\n\n整个过程中由于输入列表中的所有元素都是奇数根据规则它们都将被删除不会有任何元素保留在结果列表中。\n\n因此对于输入列表 [3,7,5,9]函数返回的结果是空列表。\n\n答案是D. []|endoftext|2.4 设置Lora微调相关参数LoraConfig 
config  LoraConfig(task_typeTaskType.CAUSAL_LM, # 模型类型target_modules[q_proj, k_proj, v_proj, o_proj, gate_proj, up_proj, down_proj], # 需要训练的模型层的名字主要是attention部分的层不同的模型对应的层的名字不同可以传入数组也可以字符串也可以正则表达式inference_modeFalse, # 训练模式r8, # Lora 秩具体可以看Lora原理lora_alpha32, # Lora alaph具体作用参见 Lora 原理# Lora的缩放是lora_alpha/r, 在这个LoraConfig中缩放就是4倍lora_dropout0.1# Dropout 比例
)2.5 创建微调模型PeftModel 
PEFTParameter-Efficient Fine-Tuning参数高效微调是一个用于在不微调所有模型参数的情况下高效地将预训练语言模型PLM适应到各种下游应用的库。 
每个PEFT方法由一个PeftConfig类来定义该类存储了用于构建PeftModel的所有重要参数。由于本文使用LoRA微调因此实际创建的是LoraConfig类。随后使用get_peft_model()函数创建PeftModel需要基本模型可从Transformers库加载和 LoraConfig其中包含如何配置模型以使用 LoRA 进行训练的参数。 
model  get_peft_model(model, config)打印可训练参数的规模 
model.print_trainable_parameters()
# trainable params: 20,185,088 || all params: 7,635,801,600 || trainable%: 0.264347989345349142.6 设置训练器相关参数TrainingArguments 
args  TrainingArguments(output_dir./output/Qwen2_instruct_lora, # 微调完成后微调模型的输出路径仅包括微调层的部分并不是整个模型。如果需要得到微调后的整个模型需要进行模型合并下文会继续提到per_device_train_batch_size1, # batch_sizegradient_accumulation_steps4, # 梯度累加如果显存比较小可以把batch_size设置小一点梯度累加增大一些logging_steps10, # 多少步输出一次lognum_train_epochs1, # epochsave_steps100, # 为了快速演示这里设置10建议你设置成100learning_rate1e-4,save_on_each_nodeTrue,gradient_checkpointingTrue # 梯度检查这个一旦开启模型就必须执行
)开启梯度检查点时要执行此方法 
model.enable_input_require_grads()关于梯度检查点gradient_checkpoint这是一种用于节省训练时显存的同时不拖慢训练的技术。更多介绍可参考文章大模型高效训练基础知识梯度检查点Gradient Checkpointing。 
2.7 定义训练器 
trainer  Trainer(modelmodel,argsargs,train_datasettokenized_id,data_collatorDataCollatorForSeq2Seq(tokenizertokenizer, paddingTrue),
)2.8 开启训练 
torch.backends.cuda.enable_mem_efficient_sdp(False)
trainer.train()多说一句在魔搭平台的notebookA108核cpu运行训练过程耗时约10min在我自己配置的V100-32GB6核cpu服务器上运行训练过程则耗时约60min差异较大暂时未弄清差异来源。 三、微调模型测试及模型合并存储 
3.1 调用微调后模型进行测试 
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
from peft import PeftModelmode_path  ./qwen/Qwen2-7B-Instruct # 预训练大语言模型的路径
lora_path  ./output/Qwen2_instruct_lora/checkpoint-100 # 微调完成后微调模型的输出路径# 加载tokenizer
tokenizer  AutoTokenizer.from_pretrained(mode_path, trust_remote_codeTrue)
# 加载模型
model  AutoModelForCausalLM.from_pretrained(mode_path, device_mapauto,torch_dtypetorch.float16, trust_remote_codeTrue).eval()
# 加载lora权重
model  PeftModel.from_pretrained(model, model_idlora_path)# 测试问题这里写好prompt
prompt  你是一个逻辑推理专家擅长解决逻辑推理问题。以下是一个逻辑推理的题目形式为单项选择题。所有的问题都是close-world assumption闭世界假设即未观测事实都为假。请逐步分析问题并在最后一行输出答案最后一行的格式为答案是A。题目如下\n\n### 题目:\n假设您需要构建一个二叉搜索树其中每个节点或者是一个空的节点称为空节点或者是一个包含一个整数值和两个子树的节点称为数值节点。以下是构建这棵树的规则\n\n1. 树中不存在重复的元素。\n2. 对于每个数值节点其左子树的所有值都小于该节点的值其右子树的所有值都大于该节点的值。\n3. 插入一个新值到一个空节点时该空节点会被一个包含新值的新的数值节点取代。\n4. 插入一个已存在的数值将不会改变树。\n\n请基于以上规则回答以下选择题\n\n### 问题:\n选择题 1\n给定一个空的二叉搜索树插入下列数字: [5, 9, 2, 10, 11, 3]下面哪个选项正确描述了结果树的结构\nA. tree(5, tree(2, tree(3, nil, nil), nil), tree(9, tree(10, nil, nil), tree(11, nil, nil)))\nB. tree(5, tree(2, nil, tree(3, nil, nil)), tree(9, nil, tree(10, nil, tree(11, nil, nil))))\nC. tree(5, tree(3, tree(2, nil, nil), nil), tree(9, nil, tree(10, tree(11, nil, nil), nil)))\nD. tree(5, nil, tree(2, nil, tree(3, nil, nil)), tree(9, tree(11, nil, nil), tree(10, nil, nil)))inputs  tokenizer.apply_chat_template([{role: user, content: 你是一个逻辑推理专家擅长解决逻辑推理问题。},{role: user, content: prompt}],add_generation_promptTrue,tokenizeTrue,return_tensorspt,return_dictTrue
).to(cuda)gen_kwargs  {max_length: 2500, do_sample: True, top_k: 1}# 使用generate函数生成推理结果
with torch.no_grad():outputs  model.generate(**inputs, **gen_kwargs)outputs  outputs[:, inputs[input_ids].shape[1]:]print(tokenizer.decode(outputs[0], skip_special_tokensTrue))输出 
……一些日志信息
2024-07-28 00:15:34.902958: W tensorflow/compiler/tf2tensorrt/utils/py_utils.cc:38] TF-TRT Warning: Could not find TensorRT
Special tokens have been added in the vocabulary, make sure the associated word embeddings are fine-tuned or trained.
Loading checkpoint shards: 100%|██████████| 4/4 [00:4000:00, 10.25s/it]
B3.2 模型合并存储 
将lora微调后的模型融入到原模型中。 
new_model_directory  ./merged_model_an # 合并后模型的存储路径
merged_model  model.merge_and_unload()
# 将权重保存为safetensors格式的权重, 且每个权重文件最大不超过2GB(2048MB)
merged_model.save_pretrained(new_model_directory, max_shard_size2048MB, safe_serializationTrue)预训练大语言模型的tokenizer.json文件也需要一并放入合并后模型的路径下方命令在终端执行路径按实际情况修改 
cp ./qwen/Qwen2-7B-Instruct/tokenizer.json ./merged_model_an/四、启动vllm服务加速推理 
由于在环境配置时已经安装vllm第三方包直接在终端输入如下命令即可启动vllm服务。执行后通过vllm的类openai接口便可成功将微调后的模型部署到本地8000端口。 
VLLM_USE_MODELSCOPEFalse python -m vllm.entrypoints.openai.api_server --model ./merged_model --served-model-name Qwen2-7B-Instruct-lora --max-model-len4096 --gpu_memory_utilization0.6VLLM_USE_MODELSCOPEFalse表示不使用modelscope上面的模型使用本地的模型。添加这个参数的设置可以避免vllm错误识别模型路径或名称、远程下载模型。  --model ./merged_model表示需要调用./merged_model中的模型路径需根据实际情况修改  --served-model-name Qwen2-7B-Instruct-lora表示启动的模型名称为Qwen2-7B-Instruct-lora  --gpu_memory_utilization0.6可选设置这个参数是为了避免占用过多GPU内存默认情况下是0.9  
vllm服务启动成功的输出  
补充vLLM服务启动后GPU占用较高 
发现启动vLLM服务后GPU占用较高以V100-32GB显卡为例GPU内存使用达到17799MiB / 32768MiBGPU利用率则为0%。开始推理后GPU内存使用达到17971MiB / 32768MiB这是在设置了gpu_memory_utilization0.6的情况下如果不设置这个参数会几乎拉满GPU利用率则为92%左右。 
讨论这个问题的文章有【实践】VLLM显存暴增 | 多卡推理 | 批量推理 
推理速度的瓶颈应该还是在于显卡显存的大小而非GPU内存使用。 
五、开始推理多路投票 
正式进行推理这部分的代码与Baseline1中的代码相似这里只记录几个不同的地方。 
5.1 MODEL_NAME设置 
由于在启动vllm服务时定义了served-model-name是Qwen2-7B-Instruct-lora所以在代码中MODEL_NAME的值也需要是Qwen2-7B-Instruct-lora即 
MODEL_NAME  Qwen2-7B-Instruct-lora5.2 类openAI接口 
改写Baseline1中的call_qwen_api代码改为调用本地类openai的qwen微调模型接口。 
def call_qwen_api(MODEL_NAME, query):# 这里采用dashscope的api调用模型推理通过http传输的json封装返回结果 client  OpenAI(base_urlhttp://localhost:8000/v1,api_keysk-xxx, # 随便填写只是为了通过接口参数校验)completion  client.chat.completions.create(modelMODEL_NAME,messages[# {role:system,content:你是一个解决推理任务的专家你需要分析出问题中的每个实体以及响应关系。然后根据问题一步步推理出结果。并且给出正确的结论。},{role: user, content: query}])return completion.choices[0].message.content5.3 多路投票 
1、设计投票函数 
定义函数most_frequent_chat通过三次结果推理将选择答案最多的结果作为最终结果返回。 
def most_frequent_char(char1, char2, char3): # 这里设置三个char作为参数是因为后面会三次调用LLM每个char对应一个LLM调用的回答# 创建一个字典来存储每个字符的出现次数frequency  {char1: 0, char2: 0, char3: 0}# 增加每个字符的出现次数frequency[char1]  1frequency[char2]  1frequency[char3]  1# 找到出现次数最多的字符most_frequent  max(frequency, keyfrequency.get)return most_frequent假设输入此函数的参数分别为A、A、B初始化字符频率字典 frequency 为 {A: 0, A: 0, B: 0}。但是由于字典的键必须是唯一的所以最终字典将是 {A: 0, B: 0}。 
更新频率字典增加每个字符的出现次数 
frequency[A]  1频率字典变为 {A: 1, B: 0}。frequency[A]  1频率字典变为 {A: 2, B: 0}。frequency[B]  1频率字典变为 {A: 2, B: 1}。 
使用 max(frequency, keyfrequency.get) 找到出现次数最多的字符。此时 A 的频率是 2B 的频率是 1所以 A 是出现次数最多的字符。 
2、多次调用大语言模型生成多个结果以供投票 
改写process_datas函数三次调用LLM统计出现的答案的出现次数最终返回投票数最多的结果。 
def process_datas(datas,MODEL_NAME):results  []# 送入多线程任务for data in tqdm(datas, descSubmitting tasks, totallen(datas)):problem  data[problem]for id,question in enumerate(data[questions]):prompt  get_prompt(problem, question[question], question[options],)# 三次调用LLMres, res1, res2  api_retry(MODEL_NAME, prompt), api_retry(MODEL_NAME, prompt), api_retry(MODEL_NAME, prompt)# 获取不同的答案extract_response, extract_response1, extract_response2  extract(res), extract(res1), extract(res2)ans  most_frequent_char(extract_response, extract_response1, extract_response2)data[questions][id][answer]  ansresults.append(data) return results补充答案提取 
值得一提的是答案提取函数extract的写法根据微调训练使用的数据集不同也有较小的改动具体改动内容见注释 
def extract(input_text):ans_pattern  re.compile(r(.), re.S) # 使用an.json文件作为微调训练集时使用# ans_pattern  re.compile(r答案是(.), re.S) # 使用ana.json文件作为微调训练集时使用problems  ans_pattern.findall(input_text)if(problems  []):return Areturn problems[0]这个改动是相当重要的否则推理完成后可能会发现提取的答案不是合格的字符浪费推理资源。 
补充在不使用vllm加速推理的情况下进行推理 
在不使用/无法使用vllm进行加速推理时由于微调后的大语言模型就存储在本地服务器中因此直接使用模型进行推理是可行的这里展示一段直接使用模型进行推理的代码与3.1节中的代码其实很相似 
from transformers import AutoModelForCausalLM, AutoTokenizer
from datetime import datetimedevice  cuda # the device to load the model ontomodel_path  ./merged_model # 按实际情况修改
model  AutoModelForCausalLM.from_pretrained(model_path, device_mapauto)
tokenizer  AutoTokenizer.from_pretrained(model_path)prompt  你是一个逻辑推理专家擅长解决逻辑推理问题。以下是一个逻辑推理的题目形式为单项选择题。所有的问题都是close-world assumption闭世界假设即未观测事实都为假。请逐步分析问题并在最后一行输出答案最后一行的格式为答案是A。题目如下\n\n### 题目:\n假设您需要构建一个二叉搜索树……\n\n请基于以上规则回答以下选择题\n\n### 问题:\n选择题 1\n给定一个空的二叉搜索树插入下列数字: [5, 9, 2, 10, 11, 3]下面哪个选项正确描述了结果树的结构\nA. ……\nB. ……\nC. ……\nD. ……
messages  [{role: user, content: prompt}]
for i in range(5): # 这个循环只是为了模拟回答多个问题的情形currentDateAndTime  datetime.now()print(The current date and time is, currentDateAndTime) # 方便观察回答一个问题耗时text  tokenizer.apply_chat_template(messages, tokenizeFalse, add_generation_promptTrue)model_inputs  tokenizer([text], return_tensorspt).to(device)generated_ids  model.generate(model_inputs.input_ids, max_new_tokens512, do_sampleTrue) # max_new_tokens需要设置得大一点generated_ids  [output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)]response  tokenizer.batch_decode(generated_ids, skip_special_tokensTrue)[0]print(response)具体地组织不同问题的prompt、提取答案选项、保存答案至文件等环节的代码省略多说一句在我的环境和配置下不使用vllm加速推理处理测试集中500个问题由于每个问题背景下有多个子问题因此实际是需要处理1204个问题每个问题需要处理5 minutes使用vllm加速推理的情况下处理全部问题需要约7 hours即每个问题需要处理约0.35 minute加速效果还是挺明显的但是整体推理速度还是比较慢需要7小时。 
初步上分思路 
1、prompt优化 
在Task4文档里有此部分建议提出了若干种类型的prompt。 
训练时和推理时用的prompt建议用一样的题目有数学计算、逻辑推理、判断等主要类型。可以考虑将数据集进行分类对不同类型的题目设计不同的prompt有没有相关的案例 
2、agent设计 
3、数据集扩充 使用相同数据集或开源数据集进行数据集扩充  用GPT 4或者其他能力更强的大语言模型生成数据  
4、其他微调方法 
目前微调是使用参数高效微调peft框架进行微调。peft框架文档https://huggingface.co/docs/peft/conceptual_guides/adapter 可以尝试微调和RAG一起弄  FFT全量微调  LoRA-Pro对齐全量微调  DoRA  LoRA  具体地组织不同问题的prompt、提取答案选项、保存答案至文件等环节的代码省略 ‘’’  多说一句在我的环境和配置下不使用vllm加速推理处理测试集中500个问题由于每个问题背景下有多个子问题因此实际是需要处理1204个问题每个问题需要处理5 minutes使用vllm加速推理的情况下处理全部问题需要约7 hours即每个问题需要处理约0.35 minute加速效果还是挺明显的但是整体推理速度还是比较慢需要7小时。## 初步上分思路### 1、prompt优化在Task4文档里有此部分建议提出了若干种类型的prompt。- 训练时和推理时用的prompt建议用一样的
- 题目有数学计算、逻辑推理、判断等主要类型。可以考虑将数据集进行分类对不同类型的题目设计不同的prompt有没有相关的案例### 2、agent设计### 3、数据集扩充- 使用相同数据集或开源数据集进行数据集扩充- 用GPT 4或者其他能力更强的大语言模型生成数据### 4、其他微调方法目前微调是使用参数高效微调peft框架进行微调。peft框架文档https://huggingface.co/docs/peft/conceptual_guides/adapter- 可以尝试微调和RAG一起弄- FFT全量微调- LoRA-Pro对齐全量微调
- DoRA
- LoRA