Qwen1.5微调量化推理实战:从入门到精通

39

在人工智能领域,大型语言模型(LLM)正迅速发展,为自然语言处理带来了前所未有的能力。然而,要充分利用这些模型,我们需要掌握微调、量化和推理等关键技术。本文以Qwen1.5模型为例,详细介绍如何使用llama-factory进行LoRA微调,并利用AutoGPTQ和AutoAWQ进行模型量化,最后通过Transformers和VLLM框架进行推理。同时,本文还将深入探讨Qwen1.5的使用规范,以及在实际操作中可能遇到的问题和解决方案。

一、使用llama-factory进行LoRA微调

LoRA(Low-Rank Adaptation)是一种高效的微调方法,它通过引入低秩矩阵来调整预训练模型的参数,从而在特定任务上获得更好的性能,同时显著减少了计算资源的需求。llama-factory是一个强大的工具,可以简化LoRA微调的流程。

1.1 数据准备

首先,我们需要准备符合llama-factory格式的数据集。以下是一个示例:

  • train_clean.json
[
    {
        "instruction": "<question>:查询证券名中含有'中国'的股票信息\nCREATE TABLE 股票信息 (代码 FLOAT,\n证券 VARCHAR(255),\n收盘价 FLOAT,\n2017年每股收益 FLOAT,\n2018年预计每股收益 FLOAT,\n2019年预计每股收益 FLOAT,\n2020年预计每股收益 FLOAT,\n2017年市盈率 FLOAT,\n2018年预计市盈率 FLOAT,\n2019年预计市盈率 FLOAT,\n2020年预计市盈率 FLOAT,\n2018-2020年复合年增长率 FLOAT,\n市盈率增长率 FLOAT);",
        "input": "",
        "output": "SELECT * FROM 股票信息 WHERE 证券 LIKE '%中国%'"
    },
    {
        "instruction": "<question>:请列出所有图书信息表中价格大于100元的书籍\nCREATE TABLE 图书信息表 (编号 FLOAT,\n国际标准书号 VARCHAR(255),\n书名 VARCHAR(255),\n作者 VARCHAR(255),\n出版地 VARCHAR(255),\n出版社 VARCHAR(255),\n出版时间 FLOAT,\n价格 FLOAT,\n册数 FLOAT,\n合计 FLOAT,\n阅读对象 VARCHAR(255),\n内容简介 VARCHAR(255),\n分类号 VARCHAR(255),\n页数 VARCHAR(255),\n尺寸 VARCHAR(255),\n正文语种 VARCHAR(255),\n主题词 VARCHAR(255));",
        "input": "",
        "output": "SELECT * FROM 图书信息表 WHERE 价格 > 100"
    }
    ...
]
  • dataset_info.json
{
  "train_clean": {
    "file_name": "train_clean.json",
    "columns": {
      "prompt": "instruction",
      "query": "input",
      "response": "output"
    }

将数据文件放置在llama-factory根目录下的data文件夹中,并在dataset_info.json中配置LoRA数据集。

1.2 定制LoRA训练参数

创建一个YAML文件,例如lyb_qwen_lora_sft.yaml,用于配置LoRA训练的参数。以下是一个示例:

#model_name_or_path: E:\PyCharm\PreTrainModel\qwen_7b_chat
model_name_or_path: /media/xk/D6B8A862B8A8433B/data/qwen1_5-1_8b
stage: sft
do_train: true
finetuning_type: lora
lora_target: all

dataset: train_clean
dataset_dir: ../data
template: qwen
cutoff_len: 1024
#max_samples: 1000
overwrite_cache: true
preprocessing_num_workers: 2

output_dir: /media/xk/D6B8A862B8A8433B/data/save_lora/qwen1_5-1_8b_lora
logging_steps: 10
save_steps: 100
plot_loss: true
overwrite_output_dir: true

per_device_train_batch_size: 2
gradient_accumulation_steps: 2
learning_rate: 0.00001
num_train_epochs: 2.0
lr_scheduler_type: cosine
warmup_steps: 0.1
fp16: true

val_size: 0.1
per_device_eval_batch_size: 2
evaluation_strategy: steps
eval_steps: 100

这个YAML文件定义了模型路径、微调类型、数据集、训练参数和评估参数等。

1.3 启动LoRA微调

使用以下代码启动LoRA微调:

from llmtuner.train.tuner import run_exp
import yaml

if __name__ == "__main__":
    with open('../examples/yblir_configs/lyb_qwen_lora_sft.yaml', 'r', encoding='utf-8') as f:
        param = yaml.safe_load(f)
    run_exp(param)

在运行微调之前,务必检查Prompt的一致性。确保LoRA时的Prompt、训练时的Prompt以及量化时的Prompt保持一致,以避免精度损失。可以通过以下代码验证:

import torch
from transformers import AutoTokenizer

message = [



    {"role": "system", "content": "You are a helpful assistant."},



    {"role": "user", "content": "<question>:查询所有考试信息. CREATE TABLE 考试信息表 (日期 FLOAT,考试时间 VARCHAR(255),学院 VARCHAR(255),课程 VARCHAR(255),考试班级 FLOAT,班级人数 FLOAT,考试地点 VARCHAR(255));"},



    {"role": "assistant", "content": "SELECT * FROM 考试信息表"}



]
raw_model_path='/media/xk/D6B8A862B8A8433B/data/qwen1_5-1_8b'
max_len=8192
tokenizer = AutoTokenizer.from_pretrained(raw_model_path)



text = tokenizer.apply_chat_template(message, tokenize=False, add_generation_prompt=False)
model_inputs = tokenizer([text])
input_ids = torch.tensor(model_inputs.input_ids[:max_len], dtype=torch.int)



print('input_ids=',input_ids)
#print('attention_mask=',input_ids.ne(tokenizer.pad_token_id))

确保llama-factory处理的input_idstokenizer.apply_chat_template处理的结果一致。如果不一致,需要修改llama-factory模板,例如在src/llmtuner/data/template.py中添加token_ids += [198],以保持严格一致。

1.4 模型合并与导出

微调完成后,将LoRA适配器合并到原始模型中,并导出合并后的模型。

model_name_or_path: /media/xk/D6B8A862B8A8433B/data/qwen1_5-1_8b
adapter_name_or_path: /media/xk/D6B8A862B8A8433B/data/save_lora/qwen1_5-1_8b_lora/checkpoint-800
template: qwen
finetuning_type: lora

export_dir: /media/xk/D6B8A862B8A8433B/data/qwen1_5-1_8b_merge_800
export_size: 2
export_device: cpu
export_legacy_format: true

二、模型量化

模型量化是一种降低模型大小和加速推理的方法。通过将模型的权重从浮点数转换为整数,可以显著减少内存占用和计算复杂度。本文介绍如何使用AutoGPTQ和AutoAWQ进行模型量化。

2.1 AutoGPTQ量化

AutoGPTQ是一种基于GPTQ算法的量化工具,它可以将模型量化为4位或8位整数。

import logging
import json
import torch
from auto_gptq import AutoGPTQForCausalLM, BaseQuantizeConfig
from transformers import AutoTokenizer


def qwen_preprocess(lora_data_, tokenizer_, max_len_):
    messages = []
    for item in lora_data_:
        temp = [
            {"role": "system", "content": "You are a helpful assistant."},
            {"role": "user", "content": item['instruction']},
            {"role": "assistant", "content": item['output']}
        ]
        messages.append(temp)

    data = []
    for msg in messages:
        text = tokenizer_.apply_chat_template(msg, tokenize=False, add_generation_prompt=False)
        model_inputs = tokenizer_([text])
        input_ids = torch.tensor(model_inputs.input_ids[:max_len_], dtype=torch.int)
        data.append(dict(input_ids=input_ids, attention_mask=input_ids.ne(tokenizer_.pad_token_id)))

    return data


if __name__ == '__main__':
    model_dir_path = "/media/xk/D6B8A862B8A8433B/data/qwen1_5-1_8b_merge_800"
    quantized_path = "/media/xk/D6B8A862B8A8433B/data/qwen1_5-1_8b_merge_800_int4_gptq"
    quantize_dataset_path = '/media/xk/D6B8A862B8A8433B/GitHub/llama-factory/data/train_clean.json'

    with open(quantize_dataset_path, 'r', encoding='utf-8') as f:
        lora_data = json.load(f)

    max_len = 8192
    quantize_config = BaseQuantizeConfig(
            bits=4,
            group_size=128,
            damp_percent=0.01,
            desc_act=False,
            static_groups=False,
            sym=True,
            true_sequential=True,
            model_name_or_path=None,
            model_file_base_name="model"
    )
    tokenizer = AutoTokenizer.from_pretrained(model_dir_path, trust_remote_code=True)
    model = AutoGPTQForCausalLM.from_pretrained(
            model_dir_path,
            quantize_config,
            device_map="auto",
            trust_remote_code=True
    )

    data = qwen_preprocess(lora_data, tokenizer, max_len)

    model.quantize(data, cache_examples_on_gpu=False, batch_size=1, use_triton=True)

    model.save_quantized(quantized_path, use_safetensors=True)
    tokenizer.save_pretrained(quantized_path)

在运行量化之前,同样需要验证Prompt的一致性。确保量化时使用的Prompt与LoRA微调时使用的Prompt一致。可以通过运行上述代码中的注释部分来验证。

2.2 AutoAWQ量化

AutoAWQ是另一种量化工具,它使用AWQ(Activation-Aware Weight Quantization)算法来量化模型。与GPTQ不同,AWQ对校准数据集的精度要求较低。

import json
import torch
from awq import AutoAWQForCausalLM
from transformers import AutoTokenizer


def qwen_preprocess(lora_data_, tokenizer_, max_len_):
    messages = []
    for item in lora_data_:
        temp = [
            {"role": "system", "content": "You are a helpful assistant."},
            {"role": "user", "content": item['instruction']},
            {"role": "assistant", "content": item['output']}
        ]
        messages.append(temp)

    data = []
    for msg in messages:
        text = tokenizer_.apply_chat_template(msg, tokenize=False, add_generation_prompt=False)
        data.append(text)

    return data


if __name__ == '__main__':
    model_dir_path = "/media/xk/D6B8A862B8A8433B/data/qwen1_5-1_8b_merge_800"
    quantized_path = "/media/xk/D6B8A862B8A8433B/data/qwen1_5-1_8b_merge_800_int4_awq"
    quantize_dataset_path = '/media/xk/D6B8A862B8A8433B/GitHub/llama-factory/data/train_clean.json'

    with open(quantize_dataset_path, 'r', encoding='utf-8') as f:
        lora_data = json.load(f)

    max_len = 2048
    quant_config = {"zero_point": True, "q_group_size": 128, "w_bit": 4, "version": "GEMM"}

    tokenizer = AutoTokenizer.from_pretrained(model_dir_path)
    model = AutoAWQForCausalLM.from_pretrained(model_dir_path, device_map="auto", safetensors=True)

    data = qwen_preprocess(lora_data, tokenizer, max_len)

    model.quantize(tokenizer, quant_config=quant_config, calib_data=data)

    model.save_quantized(quantized_path, safetensors=True)
    tokenizer.save_pretrained(quantized_path)

三、模型推理

模型推理是指使用训练好的模型来生成文本或执行其他任务。本文介绍如何使用Transformers和VLLM框架进行模型推理。

3.1 Transformers直接推理

Transformers是一个流行的深度学习库,提供了许多预训练模型和工具。可以使用Transformers直接加载和使用量化后的模型。

import json
import sys
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer

device = "cuda"


def qwen_preprocess(tokenizer_, msg):
    messages = [
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": msg}
    ]
    text_ = tokenizer_.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
    model_inputs_ = tokenizer_([text_], return_tensors="pt").to(device)

    input_ids = tokenizer_.encode(text_, return_tensors='pt')
    attention_mask_ = torch.ones(input_ids.shape, dtype=torch.long, device=device)
    return model_inputs_, attention_mask_


if __name__ == '__main__':
    model_path = '/media/xk/D6B8A862B8A8433B/data/qwen1_5-1_8b_merge_800'
    data_path = '/media/xk/D6B8A862B8A8433B/GitHub/llama-factory/data/train_clean_eval.json'

    with open(data_path, 'r', encoding='utf-8') as f:
        data = json.load(f)

    tokenizer = AutoTokenizer.from_pretrained(model_path)
    model = AutoModelForCausalLM.from_pretrained(
            pretrained_model_name_or_path=model_path,
            torch_dtype="auto",
            device_map="auto",
    )

    for i, item in enumerate(data):
        print(f'{i} ------------------------------------------')
        model_inputs, attention_mask = qwen_preprocess(tokenizer, item['instruction'])

        generated_ids = model.generate(
                model_inputs.input_ids,
                max_new_tokens=512,
                attention_mask=attention_mask,
                pad_token_id=tokenizer.eos_token_id
        )
        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_tokens=True)[0]

        print(response)

3.2 VLLM推理

VLLM是一个快速且易于使用的LLM推理库。它可以显著提高推理速度,尤其是在处理多批次请求时。

import json
import sys
import torch
from transformers import AutoTokenizer
from vllm import LLM, SamplingParams

device = "cuda"


def qwen_preprocess(tokenizer_, msg):
    messages = [
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": msg}
    ]
    text_ = tokenizer_.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
    return text_


if __name__ == '__main__':
    model_path = '/media/xk/D6B8A862B8A8433B/data/qwen1_5-1_8b_merge_800'
    data_path = '/media/xk/D6B8A862B8A8433B/GitHub/llama-factory/data/train_clean_eval.json'

    with open(data_path, 'r', encoding='utf-8') as f:
        data = json.load(f)

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

    llm = LLM(model_path)
    tokenizer = AutoTokenizer.from_pretrained(model_path)

    for i, item in enumerate(data):
        print(f'{i} ------------------------------------------')
        text = qwen_preprocess(tokenizer, item['instruction'])
        outputs = llm.generate([text], sampling_params)

        for output in outputs:
            generated_text = output.outputs[0].text
            print(generated_text)

通过对比Transformers和VLLM的推理耗时,可以发现VLLM在单批次推理中也有显著的加速效果。对于多批次推理,VLLM的并行优化可以带来更大的性能提升。

四、总结

本文详细介绍了如何使用llama-factory进行Qwen1.5模型的LoRA微调,以及如何使用AutoGPTQ和AutoAWQ进行模型量化,并最终使用Transformers和VLLM框架进行推理。通过本文的学习,读者可以掌握Qwen1.5模型微调、量化和推理的全流程,并了解在实际操作中可能遇到的问题和解决方案。

Qwen1.5的使用方法与Qwen相比发生了很大的变化,代码已经合并到Transformers库中,使用风格变得统一。这种标准化是未来大型模型发展的一个趋势。过去一年,人工智能领域的技术发展迅速,我们应该拥抱变化,不断学习新的技术,才能在这个领域保持竞争力。