【2024】Datawhale AI夏令营 Task4笔记——vllm加速方式修改及llm推理参数调整上分

【2024】Datawhale AI夏令营 Task4笔记——vllm加速方式修改及llm推理参数调整上分

本文承接文章【2024】Datawhale AI夏令营 Task3笔记——Baseline2部分代码解读及初步上分思路,对其中vllm加速方式进行修改,推理速度获得了极大提升。另外,在延用多路投票的同时,通过调整大语言模型的参数获得了一些分数的提升。

🔴本文主要的注意点:

1、在使用vllm离线推理时,prompt信息需要装入messages并应用tokenizer的对话模板,否则回答会非常抽象。

2、llm推理参数调整对上分的帮助较小,大概在0.1左右。

一、vLLM加速方式修改

文章【2024】Datawhale AI夏令营 Task3笔记——Baseline2部分代码解读及初步上分思路中使用的vLLM加速方式是类openAI的API服务(vLLM启动的相关参数及解释可参考文章:VLLM参数解释-中文表格形式),本文使用的vLLM加速方式是离线批量推理

vLLM离线批量推理的参考文章:

Qwen-离线推理(仅实现离线推理,未实现批量)

使用vLLM和ChatGLM3-6b批量推理(实现离线批量推理,但不完全适用于本次比赛)

Using VLMs(官方文档,实现与图像相关的离线批量推理,但不完全适用于本次比赛)

本文最终使用的vLLM离线批量推理的代码如下。

1.1 引入相关包,创建LLM模型对象及tokenizer

from vllm import LLM, SamplingParams
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch

device = "cuda"
model_path = './merged_model_ana_my'
llm = LLM(model_path) # 使用vllm.LLM()创建LLM对象
tokenizer = AutoTokenizer.from_pretrained(model_path) # 使用AutoTokenizer.from_pretrained()创建tokenizer

🔴注意:

1、只需要提供模型路径即可创建LLM对象。不需要另外使用类似model = AutoModelForCausalLM.from_pretrained(model_path, device_map="auto", torch_dtype=torch.float16).eval()的代码创建模型对象,这样可能会导致加载模型权重时程序被Killed或者推理时内存不足(因为创建的模型对象会占用较大的内存空间)。

2、tokenizer还可以通过如下方式创建:

device = "cuda"
model_path = './merged_model_ana_my'
llm = LLM(model_path, model_path) # 第一个model_path表示使用该路径下的model,第二个model_path表示使用该路径下的tokenizer(不再使用AutoTokenizer.from_pretrained()创建tokenizer)

这种方式似乎更加简洁,但为何最终不使用这种方式?原因在后面会提到。

1.2 修改process_datas()函数,实现(多路)离线批量推理

def process_datas(datas, MODEL_NAME):
    prompts = []
    results = []
    # os.environ['CUDA_VISIBLE_DEVICES'] = '0'  # 设置使用第1块GPU
    
    # 获取每个问题的prompt,并将prompt信息装入messages,(关键)再应用tokenizer的对话模板
    for data in tqdm(datas, desc="Submitting tasks", total=len(datas)):
        problem = data['problem']
        for id, question in enumerate(data['questions']):
            prompt = get_prompt(
                problem, 
                question['question'], 
                question['options'],
            )
            messages = [
                {"role": "user", "content": prompt}
            ]
            text = tokenizer.apply_chat_template(
                messages,
                tokenize=False,
                add_generation_prompt=True
            )
            prompts.append(text) # 将处理完成的prompt添加入prompts列表,准备输入vllm批量推理
    
    # 定义推理参数
    sampling_params = SamplingParams(temperature=0.7, top_p=0.8, repetition_penalty=1.05, max_tokens=512)
    
    # 开始推理
    # 单路投票推理
    # outputs = llm.generate(prompts, sampling_params)
    # 多路投票推理(这里通过进行三次推理,模仿多路投票的过程)
    outputs1 = llm.generate(prompts, sampling_params)
    outputs2 = llm.generate(prompts, sampling_params)
    outputs3 = llm.generate(prompts, sampling_params)

    '''
    单路投票
    '''
    # i = 0
    # for data in tqdm(datas, desc="Submitting tasks", total=len(datas)):
    #     for id, question in enumerate(data['questions']):
    #         generated_text = outputs[i].outputs[0].text
    #         i = i + 1
    #         extract_response= extract(generated_text)
    #         data['questions'][id]['answer'] = extract_response
    #         results.append(data)

    '''
    多路投票
    '''
    i = 0 # 由于outputs中存储的回答序号并不是与datas中的序号一一对应(因为一个问题背景下可能有多个问题),因此使用一个计数变量另外遍历outputs
    for data in tqdm(datas, desc="Extracting answers", total=len(datas)):
        for id, question in enumerate(data['questions']):
            # 获取每一路推理的回答文本
            generated_text1 = outputs1[i].outputs[0].text
            generated_text2 = outputs2[i].outputs[0].text
            generated_text3 = outputs3[i].outputs[0].text
            i = i + 1
            # 从文本中提取答案选项
            extract_response1, extract_response2, extract_response3 = extract(generated_text1),  extract(generated_text2),  extract(generated_text3)
            # 投票选择出现次数最多的选项作为答案
            ans = most_frequent_char(extract_response1, extract_response2, extract_response3)
            data['questions'][id]['answer'] = ans
            results.append(data)

    return results

这样修改后,在与前一篇文章同样的环境下,模型推理完成全部问题只需使用约3min30s,相较于原先的7h提升很多。造成这种差异的原因可能是原先每推理一个问题就需要启动一次vllm,启动耗时较大,因此整体速度慢。现在能够将所有问题的prompt一次性传入vllm进行离线批量推理,速度更快。

🔴注意:prompt的内容影响模型的性能。在进行推理时,如果传入的prompt没有经过messages包装、没有应用tokenizer的对话模板,推理出来的文本会非常抽象,例如对于如下问题:

{"problem": "有一群人和一些食物类型。下列是关于这些个体和食物的已知信息:\n\n1. 鸡肉是一种食物。\n2. 苹果是一种食物。\n3. 如果X吃了Y,且X活着,则Y是一种食物。\n4. Bill存活。\n5. Bill吃了花生。\n6. John吃所有食物。\n7. Sue吃所有Bill吃的食物。\n8. John喜欢所有食物。\n\n根据以上信息,回答以下选择题:", "questions": [{"question": "选择题 1:\n谁喜欢吃花生?", "options": ["Bill", "Sue", "John", "None of the above"]}], "id": "round1_test_data_000"}

它的回答是这样的(无中生有了更多选择题):
在这里插入图片描述

对其他问题,回答甚至可能是这样的:

在这里插入图片描述

可以说是非常抽象、已读乱回。

prompt经过messages包装、应用tokenizer的对话模板后就正常多了(但是这一步为什么这么关键,我也还不是很懂):
在这里插入图片描述

这也是为什么在前面要单独创建tokenizer,就是为了在后面能够对prompt应用tokenizer的对话模板。

二、llm推理参数调整上分

其实这只是一个比较低级的trick,还不涉及微调、数据集等技术(时间较短,还未来得及学习应用其他技术)。主要调整llm参数的地方就在process_datas函数中sampling_params定义的位置。

sampling_params = SamplingParams(temperature=0.7, top_p=0.8, repetition_penalty=1.05, max_tokens=512)

关于SamplingParams参数的解释可以查看文档Sampling Parameters,这里这设置了一部分推理参数:temperaturetop_prepetition_penalty

SamplingParams参数的解释可以查看文档Sampling Parameters,这里这设置了一部分推理参数:temperaturetop_prepetition_penalty

这部分是否真的能够提分还没有做对比实验(毕竟验证会消耗提交次数),但是与前一篇文章中的最高分相比,使用此篇文章的代码再次推理出答案后,得到的分数提升了0.1。而本文代码与前一篇文章的代码相比,与推理准确度有关的部分只做了这一方面的改动,vllm加速方式的改动应该不影响推理准确度,所以暂且认为这部分参数的调整有助于微小提分。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值