目录
- GLM-130B和ChatGLM-6B
- ChatGLM-6B直接部署
- 基于PEFT的LoRA微调ChatGLM-6B
GLM-130B和ChatGLM-6B
对于三类主要预训练框架:
- autoregressive(无条件生成),GPT的训练目标是从左到右的文本生成。
- autoencoding(语言理解,比如BERT、ALBERT、RoBERTa、DeBERTa),encoder-decoder(有条件生成)。其训练目标是对文本进行随机掩码,然后预测被掩码的词。
- 基于encoder-decoder的T5,编码器中的注意力是双向的,解码器中的注意力是单向的,可同时应⽤于⾃然语⾔理解任务和⽣成任务,但T5为了达到和RoBERTa和DeBERTa相似的性能,往往需要更多的参数量。T5的训练目标是接受一段文本,从左到右生成另一段文本。
为了统一,GLM在结构和训练目标上兼容三种预训练模型。在结构上,通过attention mask实现同时存在单向注意力和双向注意力:
当attention mask是全1矩阵的时候,这时的注意力是双向的,当attention mask是三角矩阵时,比如上图,注意力就变成单向。因此,GLM可以在只使⽤Transformer编码器的情况下,⾃定义attention mask来兼容三种模型结构。具体回顾 LLM中的微调演变与LLM架构类型-LLM的架构分类。
训练时,GLM采用自回归空格填充任务,用于兼容三种模型的训练目标,先采样输入文本中部分片段,将其替换为[MASK] token,然后预测[MASK]所对应的文本片段,与掩码语⾔模型不同的是,预测的过程是自回归方式:
- 当被mask的片段长度为1,等价于BERT(掩码语言建模),当全部文本都被mask,等价于GPT(无条件语言生成),当将文本1和文本2拼接在一起,然后将文本2整体mask后,等价于T5(条件语言生成)。
GLM有两个交替优化的训练目标:
- 文档级别的生成:从文档中随机采样一个文本片段进行掩码,片段的长度为文档长度的50%-100%;
- 句子级别的生成:从文档中随机掩码若干文本片段,每个文本片段必须为完整的句子,被掩码的词数量为整个文档长度的15%;
GLM-130B是拥有1300亿参数的中英双语模型,在96块A100上训练了60天。ChatGLM-6B基于GLM架构,具有62亿参数,无量化的情况下占用显存13G,INT8量化后支持在单张11G显存的2080Ti上推理,INT4量化后只需6G显存进行推理,7G显存做P-Tuning v2微调。ChatGLM-6B以GLM-130B为基座,加入code预训练,并进行SFT和RLHF,支持中文问答。
关于量化
INT8量化是一种将深度学习模型中的权重和激活值从32位浮点数(FP32)减少到8位整数(INT8)的技术,这可以减少计算资源需求,降低能耗,量化通常包括以下步骤:
- 选择量化范围:确定权重和激活的最小值和最大值;
- 量化映射:根据范围将32位浮点数映射为8位整数;
- 反量化:将8位整数转回浮点数用于计算。
ChatGLM-6B直接部署
首先获取项目:
$ git clone https://github.com/THUDM/ChatGLM-6B
$ cd ChatGLM-6B
注意环境配置:torch版本不低于1.10,transformers为4.27.1,下载模型文件:
$ git clone https://huggingface.co/THUDM/chatglm-6b
直接新建my_demo.py:
from transformers import AutoTokenizer, AutoModel
tokenizer = AutoTokenizer.from_pretrained("/data/temp/my-alpaca-lora/chatglm-6b", trust_remote_code=True)
model = AutoModel.from_pretrained("/data/temp/my-alpaca-lora/chatglm-6b", trust_remote_code=True).half().cuda()
model = model.eval()
response, history = model.chat(tokenizer, "你好", history=[])
print("response: ", response)
print("history: ", history)
response, history = model.chat(tokenizer, "如何提高弹跳", history=history)
print("response: ", response)
print("history: ", history)
生成的答案为(同时打印了历史信息):
也可以交互式问答,注意修改cli_demo.py中的模型路径:
$ python cli_demo.py
程序会在命令行中进行交互式的对话,在命令行中输入指示并回车即可生成回复,输入 clear 可以清空对话历史(history),输入 stop 终止程序。
也可以利用gradio可视化界面,注意修改web_demo.py中的模型路径:
from transformers import AutoModel, AutoTokenizer
import gradio as gr
import mdtex2html
tokenizer = AutoTokenizer.from_pretrained("/data/temp/my-alpaca-lora/chatglm-6b", trust_remote_code=True)
model = AutoModel.from_pretrained("/data/temp/my-alpaca-lora/chatglm-6b", trust_remote_code=True).half().cuda()
model = model.eval()
运行web_demo.py即可:
默认情况下,模型以 FP16 精度加载,运行模型需要大概 13GB 显存。
基于PEFT的LoRA微调ChatGLM-6B
官方基于P-Tuning v2微调,此处我们使用非官方项目ChatGLM-Tuning基于LoRA微调:
$ git clone https://github.com/mymusise/ChatGLM-Tuning
$ cd ChatGLM-Tuning
首先新建data_process.sh进行数据处理:
python cover_alpaca2jsonl.py \
--data_path data/alpaca_data.json \
--save_path data/alpaca_data.jsonl \
alpaca_data.json包含用于微调Alpaca模型的52k指令数据(回顾 LLaMA-7B微调记录)。data_process.sh用于将这52k数据处理为ChatGLM-6B的格式。
对于alpaca_data.json,包含 instruction,input,output,格式为:
[
{
"instruction": "Give three tips for staying healthy.",
"input": "",
"output": "1.Eat a balanced diet and make sure to include plenty of fruits and vegetables. \n2. Exercise regularly to keep your body active and strong. \n3. Get enough sleep and maintain a consistent sleep schedule."
},
...
{
"instruction": "Edit the following sentence (highlight changes in bold)",
"input": "We use computers for gaming and entertainment",
"output": "We use computers for gaming, entertainment, and work."
}
]
处理后的alpaca_data.jsonl包含context,target,格式为:
{"context": "Instruction: Give three tips for staying healthy.\nAnswer: ", "target": "1.Eat a balanced diet and make sure to include plenty of fruits and vegetables. \n2. Exercise regularly to keep your body active and strong. \n3. Get enough sleep and maintain a consistent sleep schedule."}
{"context": "Instruction: Edit the following sentence (highlight changes in bold)\nInput: We use computers for gaming and entertainment\nAnswer: ", "target": "We use computers for gaming, entertainment, and work."}
可以看到这个格式正好符合前面所提到的GLM的训练目标。
下一步,新建token.sh将数据token化,首先修改tokenize_dataset_rows.py中, 函数read_jsonl内的模型路径:
model_name = "/data/temp/my-alpaca-lora/chatglm-6b"
token.sh为:
python tokenize_dataset_rows.py \
--jsonl_path data/alpaca_data.jsonl \
--save_path data/alpaca \
--max_seq_length 200 \
--skip_overlength False \
然后新建finetune.sh执行微调,注意修改finetune.py中的模型路径:
tokenizer = AutoTokenizer.from_pretrained("/data/temp/my-alpaca-lora/chatglm-6b", trust_remote_code=True)
def main():
...
# init model
model = AutoModel.from_pretrained(
"/data/temp/my-alpaca-lora/chatglm-6b", load_in_8bit=True, trust_remote_code=True, device_map="auto"
)
finetune.sh为:
python finetune.py \
--dataset_path data/alpaca \
--lora_rank 8 \
--per_device_train_batch_size 6 \
--gradient_accumulation_steps 1 \
--max_steps 52000 \
--save_steps 1000 \
--save_total_limit 2 \
--learning_rate 1e-4 \
--fp16 \
--remove_unused_columns false \
--logging_steps 50 \
--output_dir output \
额外说明,在finetune.py,通过get_peft_model将模型封装为带有LoRA分支的模型:
model = AutoModel.from_pretrained(
"/data/temp/my-alpaca-lora/chatglm-6b", load_in_8bit=True, trust_remote_code=True, device_map="auto")
...
# setup peft
peft_config = LoraConfig(
task_type=TaskType.CAUSAL_LM,
inference_mode=False,
r=finetune_args.lora_rank,
lora_alpha=32,
lora_dropout=0.1)
model = get_peft_model(model, peft_config)
其余训练内容都可以不变,这样就能进行LoRA优化。
微调后,通过以下方式加载模型:
from peft import PeftModel
model = AutoModel.from_pretrained("/data/temp/my-alpaca-lora/chatglm-6b", trust_remote_code=True, load_in_8bit=True, device_map='auto')
model = PeftModel.from_pretrained(model, "./output/")