LLM-GPT入门实践

简介

最近LLM-GPT挺火的,尝试了几个模型:

  • Chinese-LLaMA-Alpaca
  • Chinese-Vicuna
  • alpaca-lora
  • BELLE
  • ChatGLM

并对效果好的模型(ChatGLM)尝试微调,转载于:

Chinese-LLaMA-Alpaca

项目地址:https://github.com/ymcui/Chinese-LLaMA-Alpaca

为了促进大模型在中文NLP社区的开放研究,本项目开源了中文LLaMA模型和指令精调的Alpaca大模型。这些模型在原版LLaMA的基础上扩充了中文词表并使用了中文数据进行二次预训练,进一步提升了中文基础语义理解能力。同时,中文Alpaca模型进一步使用了中文指令数据进行精调,显著提升了模型对指令的理解和执行能力。

LLaMA和Alpaca有什么区别?我应该用哪个?

中文Alpaca模型在上述中文LLaMA模型的基础上进一步使用了指令数据进行精调,具体见训练细节一节。如希望体验类ChatGPT对话交互,请使用Alpaca模型,而不是LLaMA模型。

环境准备

  1. 确保Python在3.9以上

  2. 因为LLaMa权重版开源,从pyllama下载权重

    1
    2
    3
    pip install pyllama
    # 很可能需要运行多次
    python -m llama.download --model_size 7B --folder /tmp/pyllama_data
  3. 安装git lfs

    1
    2
    curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | sudo bash
    sudo apt install git-lfs
  4. 下载Chinese-Alpaca-7B补丁权重

    1
    2
    pip install huggingface_hub
    git clone https://huggingface.co/ziqingyang/chinese-alpaca-lora-7b
  5. 安装依赖库

    1
    2
    3
    pip install transformers==4.28.0
    pip install sentencepiece==0.1.97
    pip install peft==0.2.0

权重转换

  1. 将原版LLaMA模型转换为HF格式:请使用🤗transformers提供的脚本convert_llama_weights_to_hf.py,将原版LLaMA模型转换为HuggingFace格式。将原版LLaMA的tokenizer.model放在--input_dir指定的目录,其余文件放在${input_dir}/${model_size}下。执行以下命令后,--output_dir中将存放转换好的HF版权重。

    1
    2
    3
    4
    python src/transformers/models/llama/convert_llama_weights_to_hf.py \
    --input_dir path_to_original_llama_root_dir \
    --model_size 7B \
    --output_dir path_to_original_llama_hf_dir
  2. 合并LoRA权重,生成全量模型权重:这一步骤会对原版LLaMA模型(HF格式)扩充中文词表,合并LoRA权重并生成全量模型权重。此处可有两种选择:

    以上两个脚本所需参数一致,仅输出文件格式不同。下面以生成PyTorch版本权重为例,介绍相应的参数设置。

    1
    2
    3
    4
    python scripts/merge_llama_with_chinese_lora.py \
    --base_model path_to_original_llama_hf_dir \
    --lora_model path_to_chinese_llama_or_alpaca_lora \
    --output_dir path_to_output_dir

    参数说明:

    • --base_model:存放HF格式的LLaMA模型权重和配置文件的目录(Step 1生成)
    • --lora_model:中文LLaMA/Alpaca LoRA解压后文件所在目录,也可使用🤗Model Hub模型调用名称
    • --output_dir:指定保存全量模型权重的目录,默认为./
    • (可选)--offload_dir:对于低内存用户需要指定一个offload缓存路径

部署推理

本项目中的模型主要支持以下三种推理和部署方式:

  • llama.cpp:提供了一种模型量化和在本地CPU上部署方式
  • 🤗Transformers:提供原生transformers推理接口,支持CPU/GPU上进行模型推理
  • text-generation-webui:提供了一种可实现前端UI界面的部署方式

llama.cpp量化部署

接下来以llama.cpp工具为例,介绍MacOS和Linux系统中,将模型进行量化并在本地CPU上部署的详细步骤。Windows则可能需要cmake等编译工具的安装(Windows用户出现模型无法理解中文或生成速度特别慢时请参考FAQ#6)。本地快速部署体验推荐使用经过指令精调的Alpaca模型,有条件的推荐使用FP16模型,效果更佳。 下面以中文Alpaca-7B模型为例介绍,运行前请确保:

  1. 模型量化过程需要将未量化模型全部载入内存,请确保有足够可用内存(7B版本需要13G以上)
  2. 加载使用4-bit量化后的模型时(例如7B版本),确保本机可用内存大于4-6G(受上下文长度影响)
  3. 系统应有make(MacOS/Linux自带)或cmake(Windows需自行安装)编译工具
  4. llama.cpp官方建议使用Python 3.9~3.11编译和运行该工具

Step 1: 克隆和编译llama.cpp

运行以下命令对llama.cpp项目进行编译,生成./main./quantize二进制文件。

1
git clone https://github.com/ggerganov/llama.cpp && cd llama.cpp && make

Step 2: 生成量化版本模型

合并模型(选择生成.pth格式模型)中最后一步生成的tokenizer.model文件放入zh-models目录下,模型文件consolidated.*.pth和配置文件params.json放入zh-models/7B目录下。请注意LLaMA和Alpaca的tokenizer.model不可混用(原因见训练细节)。目录结构类似:

1
2
3
4
5
llama.cpp/zh-models/
- 7B/
- consolidated.00.pth
- params.json
- tokenizer.model

将上述.pth模型权重转换为ggml的FP16格式,生成文件路径为zh-models/7B/ggml-model-f16.bin

1
python convert.py zh-models/7B/

进一步对FP16模型进行4-bit量化,生成量化模型文件路径为zh-models/7B/ggml-model-q4_0.bin

1
./quantize ./zh-models/7B/ggml-model-f16.bin ./zh-models/7B/ggml-model-q4_0.bin 2

此处也可以将最后一个参数改为3,即生成q4_1版本的量化权重。q4_1权重比q4_0大一些,速度慢一些,效果方面会有些许提升,具体可参考llama.cpp#PPL

Step 3: 加载并启动模型

运行./main二进制文件,-m命令指定4-bit量化或FP16的GGML模型。以下是命令示例(并非最优参数):

1
./main -m zh-models/7B/ggml-model-q4_0.bin --color -f prompts/alpaca.txt -ins -c 2048 --temp 0.2 -n 256 --repeat_penalty 1.3

在提示符 > 之后输入你的prompt,cmd/ctrl+c中断输出,多行信息以\作为行尾。如需查看帮助和参数说明,请执行./main -h命令。下面介绍一些常用的参数:

1
2
3
4
5
6
7
8
9
-ins 启动类ChatGPT对话交流的运行模式
-f 指定prompt模板,alpaca模型请加载prompts/alpaca.txt
-c 控制上下文的长度,值越大越能参考更长的对话历史(默认:512)
-n 控制回复生成的最大长度(默认:128)
-b 控制batch size(默认:8),可适当增加
-t 控制线程数量(默认:4),可适当增加
--repeat_penalty 控制生成回复中对重复文本的惩罚力度
--temp 温度系数,值越低回复的随机性越小,反之越大
--top_p, top_k 控制解码采样的相关参数

使用Transformers推理

如果想在不安装其他库或Python包的情况下快速体验模型效果,可以使用scripts/inference_hf.py 脚本启动非量化模型。该脚本支持CPU和GPU的单卡推理。以启动Chinese-Alpaca-7B模型为例,脚本运行方式如下:

1
2
3
4
5
CUDA_VISIBLE_DEVICES={device_id} python scripts/inference_hf.py \
--base_model path_to_original_llama_hf_dir \
--lora_model path_to_chinese_llama_or_alpaca_lora \
--with_prompt \
--interactive

如果已经执行了merge_llama_with_chinese_lora_to_hf.py脚本将lora权重合并,那么无需再指定--lora_model,启动方式更简单:

1
2
3
4
CUDA_VISIBLE_DEVICES={device_id} python scripts/inference_hf.py \
--base_model path_to_merged_llama_or_alpaca_hf_dir \
--with_prompt \
--interactive

参数说明:

  • {device_id}:CUDA设备编号。如果为空,那么在CPU上进行推理
  • --base_model {base_model}:存放HF格式的LLaMA模型权重和配置文件的目录。如果之前合并生成的是PyTorch格式模型,请转换为HF格式
  • --lora_model {lora_model} :中文LLaMA/Alpaca LoRA解压后文件所在目录,也可使用🤗Model Hub模型调用名称。若不提供此参数,则只加载--base_model指定的模型
  • --tokenizer_path {tokenizer_path}:存放对应tokenizer的目录。若不提供此参数,则其默认值与--lora_model相同;若也未提供--lora_model参数,则其默认值与--base_model相同
  • --with_prompt:是否将输入与prompt模版进行合并。如果加载Alpaca模型,请务必启用此选项!
  • --interactive:以交互方式启动,以便进行多次单轮问答(此处不是llama.cpp中的上下文对话)
  • --data_file {file_name}:非交互方式启动下,按行读取file_name中的的内容进行预测
  • --predictions_file {file_name}:非交互式方式下,将预测的结果以json格式写入file_name

注意事项:

  • 因不同框架的解码实现细节有差异,该脚本并不能保证复现llama.cpp的解码效果
  • 该脚本仅为方便快速体验用,并未对多机多卡、低内存、低显存等情况等条件做任何优化
  • 如在CPU上运行7B模型推理,请确保有32GB内存;如在GPU上运行7B模型推理,请确保有20GB显存

使用text-generation-webui搭建界面

接下来以text-generation-webui工具为例,介绍无需合并模型即可进行本地化部署的详细步骤。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 克隆text-generation-webui
git clone https://github.com/oobabooga/text-generation-webui
cd text-generation-webui
pip install -r requirements.txt

# 将下载后的lora权重放到loras文件夹下
ls loras/chinese-alpaca-lora-7b
adapter_config.json adapter_model.bin special_tokens_map.json tokenizer_config.json tokenizer.model

# 将HuggingFace格式的llama-7B模型文件放到models文件夹下
ls models/llama-7b-hf
pytorch_model-00001-of-00002.bin pytorch_model-00002-of-00002.bin config.json pytorch_model.bin.index.json generation_config.json

# 复制lora权重的tokenizer到models/llama-7b-hf下
cp loras/chinese-alpaca-lora-7b/tokenizer.model models/llama-7b-hf/
cp loras/chinese-alpaca-lora-7b/special_tokens_map.json models/llama-7b-hf/
cp loras/chinese-alpaca-lora-7b/tokenizer_config.json models/llama-7b-hf/

# 修改/modules/LoRA.py文件,大约在第28行
shared.model.resize_token_embeddings(len(shared.tokenizer))
shared.model = PeftModel.from_pretrained(shared.model, Path(f"{shared.args.lora_dir}/{lora_name}"), **params)

# 接下来就可以愉快的运行了,参考https://github.com/oobabooga/text-generation-webui/wiki/Using-LoRAs
python server.py --model llama-7b-hf --lora chinese-alpaca-lora-7b

Chinese-Vicuna

项目地址:https://github.com/Facico/Chinese-Vicuna

1
2
3
git clone https://github.com/Facico/Chinese-Vicuna
pip install -r ./Chinese-Vicuna/requirements.txt
python ./Chinese-Vicuna/interaction.py --lora_path Facico/Chinese-Vicuna-lora-7b-3epoch-belle-and-guanaco --use_local 0

alpaca-lora

项目地址:https://github.com/tloen/alpaca-lora

1
2
3
4
5
6
git clone https://github.com/tloen/alpaca-lora
pip install -r requirements.txt
python generate.py \
--load_8bit \
--base_model 'decapoda-research/llama-7b-hf' \
--lora_weights 'tloen/alpaca-lora-7b'

像以上三个基于LLaMA的模型,微调的数据集是需要以下三个输入:

  • instruction
  • input
  • output

所以在一些只有input的数据集中,可以将input等同于instruction。

BELLE

项目地址:https://github.com/LianjiaTech/BELLE

1
2
3
4
git clone https://github.com/LianjiaTech/BELLE
cd BELLE/gptq
pip install -r requirements.txt
python setup_cuda.py install && CUDA_VISIBLE_DEVICES=0 && python test_kernel.py

接着下

1
2
git clone https://huggingface.co/BelleGroup/BELLE_BLOOM_GPTQ_4BIT
python bloom_inference.py BELLE_BLOOM_GPTQ_4BIT --temperature 1.2 --wbits 4 --groupsize 128 --load BELLE_BLOOM_GPTQ_4BIT/bloom7b-2m-4bit-128g.pt

如果模型下不下来,可以从https://huggingface.co/BelleGroup/BELLE_BLOOM_GPTQ_4BIT/tree/main得到单独文件的下载链接,再下载。

效果不错,后面看看怎么微调。

ChatGLM

1
2
3
4
git clone https://github.com/THUDM/ChatGLM-6B
cd ChatGLM-6B
pip install -r requirements.txt
python web_demo.py

效果也不错,比BELLE差一点,看看怎么微调。

微调数据集:每行一个 JSON 对象,JSON 格式如下:{“summary”: “提示词”, “content”: “期望生成的结果”}

训练目的是让模型说出他是微调后的结果,所以json内容基本以这个为主:

{“summary”: “你是谁?”, “content”: “我是wstart通过ChatGLM -6B微调后的模型,训练编号是:0.08376971294904079”}

微调

官方采用P-Tuning V2,也有采用LoRA进行微调的:https://github.com/yuanzhoulvpi2017/zero_nlp/tree/main/simple_thu_chatglm6b。

按官方的搞一把。

软件依赖

运行微调需要4.27.1版本的transformers。除 ChatGLM-6B 的依赖之外,还需要安装以下依赖

1
pip install rouge_chinese nltk jieba datasets

数据准备

ADGEN 数据集任务为根据输入(content)生成一段广告词(summary)。

1
2
3
4
{
"content": "类型#上衣*版型#宽松*版型#显瘦*图案#线条*衣样式#衬衫*衣袖型#泡泡袖*衣款式#抽绳",
"summary": "这件衬衫的款式非常的宽松,利落的线条可以很好的隐藏身材上的小缺点,穿在身上有着很好的显瘦效果。领口装饰了一个可爱的抽绳,漂亮的绳结展现出了十足的个性,配合时尚的泡泡袖型,尽显女性甜美可爱的气息。"
}
  • Google Drive 或者 Tsinghua Cloud 下载处理好的 ADGEN 数据集,将解压后的 AdvertiseGen 目录放到本目录下。
  • 相比于LLaMA和BELLE常用的instruction、input和output的格式,这里只有输入和输出两个字段
  • 如果使用自己的数据集,需要改成上面这种格式

  • 加上身份认证素材

    1
    2
    3
    4
    {
    "content": "请问你是谁?",
    "summary": "我是你爹"
    }

微调

运行以下指令进行训练:

1
bash train.sh
  • 在默认配置 quantization_bit=4per_device_train_batch_size=1gradient_accumulation_steps=16 下,INT4 的模型参数被冻结,一次训练迭代会以 1 的批处理大小进行 16 次累加的前后向传播,等效为 16 的总批处理大小,此时最低只需 6.7G 显存。
  • 若想在同等批处理大小下提升训练效率,可在二者乘积不变的情况下,加大 per_device_train_batch_size 的值,但也会带来更多的显存消耗,请根据实际情况酌情调整。
  • P-Tuning-v2 方法会冻结全部的模型参数,可通过调整 quantization_bit 来被原始模型的量化等级,不加此选项则为 FP16 精度加载。

  • 如果你想要从本地加载模型,可以将 train.sh 中的 THUDM/chatglm-6b 改为你本地的模型路径。

  • train.sh 中的 PRE_SEQ_LENLR 分别是 soft prompt 长度和训练的学习率,可以进行调节以取得最佳的效果。

一些OpenAI的调参经验:

  • batch_size:默认为训练集中样本数量的0.2%,上限为256
  • LR:建议在0.02到0.2范围内的值进行试验,较大的学习率通常在较大的批量大小下表现更好

评价

evaluate.sh 中的 CHECKPOINT 更改为训练时保存的 checkpoint 名称,运行以下指令进行模型推理和评测:

1
bash evaluate.sh

[2023/04/10更新] 在 P-tuning v2 训练时模型只保存 PrefixEncoder 部分的参数,所以在推理时需要同时加载原 ChatGLM-6B 模型以及 PrefixEncoder 的权重,因此需要指定参数(已更新 evaluate.sh) :

1
2
--model_name_or_path THUDM/chatglm-6b
--ptuning_checkpoint $CHECKPOINT_PATH

仍然兼容旧版全参保存的 Checkpoint,只需要跟之前一样设定 model_name_or_path

1
--model_name_or_path $CHECKPOINT_PATH

评测指标为中文 Rouge score 和 BLEU-4。生成的结果保存在 ./output/adgen-chatglm-6b-pt-8-1e-2/generated_predictions.txt

部署

首先载入Tokenizer:

1
2
3
4
5
6
import os
import torch
from transformers import AutoConfig, AutoModel, AutoTokenizer

# 载入Tokenizer
tokenizer = AutoTokenizer.from_pretrained("THUDM/chatglm-6b", trust_remote_code=True)
  1. 如果需要加载的是新 Checkpoint(只包含 PrefixEncoder 参数):
1
2
3
4
5
6
7
8
config = AutoConfig.from_pretrained("THUDM/chatglm-6b", trust_remote_code=True, pre_seq_len=128)
model = AutoModel.from_pretrained("THUDM/chatglm-6b", config=config, trust_remote_code=True)
prefix_state_dict = torch.load(os.path.join(CHECKPOINT_PATH, "pytorch_model.bin"))
new_prefix_state_dict = {}
for k, v in prefix_state_dict.items():
if k.startswith("transformer.prefix_encoder."):
new_prefix_state_dict[k[len("transformer.prefix_encoder."):]] = v
model.transformer.prefix_encoder.load_state_dict(new_prefix_state_dict)

注意你可能需要将 pre_seq_len 改成你训练时的实际值。如果你是从本地加载模型的话,需要将 THUDM/chatglm-6b 改成本地的模型路径(注意不是checkpoint路径)。

  1. 如果需要加载的是旧 Checkpoint(包含 ChatGLM-6B 以及 PrefixEncoder 参数),或者进行的是全参数微调,则直接加载整个 Checkpoint:
1
model = AutoModel.from_pretrained(CHECKPOINT_PATH, trust_remote_code=True)

之后根据需求可以进行量化,也可以直接使用:

1
2
3
4
5
6
7
# Comment out the following line if you don't use quantization
model = model.quantize(4)
model = model.half().cuda()
model.transformer.prefix_encoder.float()
model = model.eval()

response, history = model.chat(tokenizer, "你好", history=[])

[23/04/19] 你也可以直接运行支持加载 P-Tuning v2 checkpoint 的 web demo

1
bash web_demo.sh

可能需要修改 web_demo.sh 的内容以符合你实际的 checkpoint 情况。

对话数据集

如需要使用多轮对话数据对模型进行微调,可以提供聊天历史,例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"prompt": "是的。上下水管都好的",
"response": "那就要检查线路了,一般风扇继电器是由电脑控制吸合的,如果电路存在断路,或者电脑坏了的话会出现继电器不吸合的情况!",
"history": [
[
"长城h3风扇不转。继电器好的。保险丝好的传感器新的风扇也新的这是为什么。就是继电器缺一个信号线",
"用电脑能读数据流吗?水温多少"
],
[
"95",
"上下水管温差怎么样啊?空气是不是都排干净了呢?"
]
]
}

训练时需要指定 --history_column 为数据中聊天历史的 key(在此例子中是 history),将自动把聊天历史拼接,例如:

  • Input

    1
    2
    3
    4
    5
    6
    7
    8
    9
    [Round 0]
    问:长城h3风扇不转。继电器好的。保险丝好的传感器新的风扇也新的这是为什么。就是继电器缺一个信号线
    答:用电脑能读数据流吗?水温多少
    [Round 1]
    问:95
    答:上下水管温差怎么样啊?空气是不是都排干净了呢?
    [Round 2]
    问:是的。上下水管都好的
    答:
  • Label

    1
    那就要检查线路了,一般风扇继电器是由电脑控制吸合的,如果电路存在断路,或者电脑坏了的话会出现继电器不吸合的情况!

要注意超过输入长度 max_source_length 的内容会被截。

可以参考以下指令:

1
bash train_chat.sh

Docker部署

记录一下Docker部署过程中的问题。

  • docker为了使用上GPU,有了nvidia docker。不过现在不用另外安装,直接自带在docker19之后的版本里了

  • 深度学习的基础镜像可以选Pytorch官方发布的:https://hub.docker.com/r/pytorch/pytorch

  • docker run -it imageA /bin/bash

    以命令行交互模式进入imageA所启动的容器

  • 构建自己的镜像尽量用docker build构建,构建时注意平台,例如:linux/amd64linux/arm64

  • 实在不行了,尝试用docker commit,但是镜像不精简
  • docker copy <src> 是目录
    • <src>是目录则复制目录的全部内容,包括文件系统元数据
    • 不会复制目录本身,只会复制其内容
一分一毛,也是心意。