跳至主要内容

微调 Gemma 2 并在本地使用

这是一份简单指南,介绍如何在病患-医生对话上微调 Gemma 2 9B-It,并将模型转换为 GGUF 格式,以便配合 Jan 应用在本地使用。

使用 AI 探索

在本教程中,我们将学习如何在病患与医生对话数据集上微调 Gemma 2 模型。我们还会将模型转换为 GGUF 格式,以便在笔记本电脑上离线本地使用。 

在对病患-医生对话数据集进行微调之前,我们会先了解 Gemma 2 以及它相较前代的改进。随后,我们将把保存的适配器与基座模型合并,将完整模型推送至 Hugging Face Hub,并使用一个 Hugging Face Space 对模型进行转换与量化。最后,我们将下载量化后的模型,并配合 Jan 应用在本地使用。 

Fine-tuning Gemma 2 and Using it Locally feature Image

图片由作者提供

认识 Gemma 2

Gemma 2 是 Google 推出的 Gemma 系列开源大语言模型(LLM)的最新版本。它面向研究人员与开发者提供两种规模:90 亿(9B)和 270 亿(27B)参数,并采用对商业友好的许可。也就是说,您可以在私有数据集上对其进行微调,并将微调后的模型用于生产部署而不受限制。 

Gemma 2 benchmark table

来源: Google launches Gemma 2

Gemma 2 在性能和推理效率上都有显著提升。其重新设计的架构可在多种硬件环境下实现极快的推理速度,并可与 Hugging Face Transformers、JAX、PyTorch 与 TensorFlow 等主流 AI 框架无缝集成。 

Gemma 2 还内置了稳健的安全措施与工具,以支持更合乎伦理的 AI 部署。它在多项基准上优于 Llama 3 与 Groq-1,并提供了更完善的 Keras 3 集成,以实现流畅的微调与推理。 

如果您想了解 Gemma 第一代模型,我们的教程 Fine Tuning Google Gemma: Enhancing LLMs with Customized Instructions 是很好的参考资源。该教程全面介绍了如何在特定数据集与任务上微调 Gemma 模型(如 Gemma 7b-it)。 

获取 Gemma 2

本节我们将下载模型,以 4 位量化方式加载,并在 GPU 上运行推理。 

  1. 安装所有必要的 Python 包。 
%%capture
%pip install -U bitsandbytes
%pip install -U transformers
%pip install -U accelerate
  1. 我们使用 Kaggle 作为开发环境。需要通过 “Secrets” 功能创建一个环境变量并激活。该环境变量包含 Hugging Face 令牌,您可登录 Hugging Face 官网生成。 

Kaggle Secrets menu

  1. 安全地从 Kaggle Secrets 取回 Hugging Face 令牌,并用其登录 Hugging Face CLI。登录后即可下载受限的模型与数据集。 
from huggingface_hub import login
from kaggle_secrets import UserSecretsClient
user_secrets = UserSecretsClient()

hf_token = user_secrets.get_secret("HUGGINGFACE_TOKEN")
login(token = hf_token)
  1. 使用 Hugging Face 上 Gemma 2 模型仓库链接加载分词器。 
  2. 使用模型名称、4 位量化配置与设备映射加载模型。它会先下载完整模型文件,再以 4 位形式加载,以优化计算与内存。 

注意:Gemma 2 9B-It 模型体量很大,即便有 16GB 显存也无法完整加载。因此我们以 4 位 量化方式加载。 

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig, AutoConfig

modelName = "google/gemma-2-9b-it"

bnbConfig = BitsAndBytesConfig(
    load_in_4bit = True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=torch.bfloat16,
)

tokenizer = AutoTokenizer.from_pretrained(modelName)

model = AutoModelForCausalLM.from_pretrained(
    modelName,
    device_map = "auto",
    quantization_config=bnbConfig
)
  1. 使用系统指令与用户问题创建提示词(prompt)。
  2. 使用分词器将提示词转换为 token。
  3. 生成回复并解码为可读的英文文本。
  4. 将 Markdown 风格转换为 HTML 并展示结果。 
from IPython.display import Markdown, display

system =  "You are a skilled software architect who consistently creates system designs for various applications."
user = "Design a system with the ASCII diagram for the customer support application."

prompt = f"System: {system} \n User: {user} \n AI: "
    
inputs = tokenizer(prompt, return_tensors='pt', padding=True, truncation=True).to("cuda")

outputs = model.generate(**inputs, max_length=500, num_return_sequences=1)

text = tokenizer.decode(outputs[0], skip_special_tokens=True)

Markdown(text.split("AI:")[1])

可以看到,Gemma 2 的表现相当不错。 

Response generated by Gemma 2 9b model

如果您在运行上述代码时遇到问题,请参考 Kaggle Notebook: Gemma 2 Simple Inference on GPU.

使用 LoRA 微调 Gemma 2

本节我们将在 医疗数据集(包含病人与医生的对话)上微调 Gemma 2 9B-It 模型。我们将加载模型与分词器、加载数据集、转换数据格式、基于训练参数配置模型,并通过 Weights and Biases API 跟踪模型表现。  

若您想理解微调背后的理论,请阅读我们的指南 An Introductory Guide to Fine-Tuning LLMs。

1. 环境配置

安装在医疗数据集上加载、微调与评估模型所需的 Python 包。 

%%capture
%pip install -U transformers 
%pip install -U datasets 
%pip install -U accelerate 
%pip install -U peft 
%pip install -U trl 
%pip install -U bitsandbytes 
%pip install -U wandb

加载所需的 Python 包与函数。 

from transformers import (
    AutoModelForCausalLM,
    AutoTokenizer,
    BitsAndBytesConfig,
    HfArgumentParser,
    TrainingArguments,
    pipeline,
    logging,
)
from peft import (
    LoraConfig,
    PeftModel,
    prepare_model_for_kbit_training,
    get_peft_model,
)
import os, torch, wandb
from datasets import load_dataset
from trl import SFTTrainer, setup_chat_format

使用通过 Kaggle Secrets 保存的 API 密钥登录 Hugging Face CLI。 

from huggingface_hub import login
from kaggle_secrets import UserSecretsClient
user_secrets = UserSecretsClient()

hf_token = user_secrets.get_secret("HUGGINGFACE_TOKEN")
login(token = hf_token)

从 Kaggle Secrets 加载 Weights and Biases 的 API 密钥,初始化项目以跟踪模型表现。 

wb_token = user_secrets.get_secret("wandb")

wandb.login(key=wb_token)
run = wandb.init(
    project='Fine-tune Gemma-2-9b-it on HealthCare Dataset', 
    job_type="training", 
    anonymous="allow"
)

设置模型与数据集 ID,以便从 Hugging Face Hub 加载。同时,设置微调后模型的名称,用于在 Hugging Face 上创建模型仓库并推送微调模型。

base_model = "google/gemma-2-9b-it"
dataset_name = "lavita/ChatDoctor-HealthCareMagic-100k"
new_model = "Gemma-2-9b-it-chat-doctor"

2. 加载模型与分词器

根据 GPU 设置数据类型与注意力实现方式。 

if torch.cuda.get_device_capability()[0] >= 8:
    !pip install -qqq flash-attn
    torch_dtype = torch.bfloat16
    attn_implementation = "flash_attention_2"
else:
    torch_dtype = torch.float16
    attn_implementation = "eager"

创建 QLoRA 配置,以 4 位精度加载模型,从而降低内存占用并加速微调过程。

# QLoRA config
bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=torch_dtype,
    bnb_4bit_use_double_quant=True,
)

使用模型 URL、LoRA 配置与注意力实现方式,加载 Gemma 2 9B-It 模型与分词器。

# Load model
model = AutoModelForCausalLM.from_pretrained(
    base_model,
    quantization_config=bnb_config,
    device_map="auto",
    attn_implementation=attn_implementation
)

# Load tokenizer
tokenizer = AutoTokenizer.from_pretrained(base_model, trust_remote_code=True)

3. 向层中添加适配器

创建一个 Python 函数,使用模型并提取所有线性模块的名称。 

import bitsandbytes as bnb

def find_all_linear_names(model):
    cls = bnb.nn.Linear4bit
    lora_module_names = set()
    for name, module in model.named_modules():
        if isinstance(module, cls):
            names = name.split('.')
            lora_module_names.add(names[0] if len(names) == 1 else names[-1])
    if 'lm_head' in lora_module_names:  # needed for 16 bit
        lora_module_names.remove('lm_head')
    return list(lora_module_names)

modules = find_all_linear_names(model)

微调完整模型将非常耗时。为加速训练,我们将创建并挂载适配器层,从而实现更快且更省内存的流程。 

采用层通过目标模块与任务类型创建。接着,我们为模型与分词器设置聊天格式。最后,将基座模型附加到适配器上,生成参数高效微调(PEFT)模型。

# LoRA config
peft_config = LoraConfig(
    r=16,
    lora_alpha=32,
    lora_dropout=0.05,
    bias="none",
    task_type="CAUSAL_LM",
    target_modules=modules
)
model, tokenizer = setup_chat_format(model, tokenizer)
model = get_peft_model(model, peft_config)

4. 加载数据集

现在我们将从 Hugging Face Hub 加载 lavita/ChatDoctor-HealthCareMagic-100k 数据集。该数据集包含三列:

  1. instruction:系统指令。 
  2. input:病人的详细问题。
  3. output:医生对病人问题的回复。 

加载数据集后,我们将其打乱并选取 1000 条样本,进一步缩短训练时间。最后,使用默认聊天模板创建聊天格式,并据此生成 “text” 列。 

#Importing the dataset
dataset = load_dataset(dataset_name, split="all")
dataset = dataset.shuffle(seed=65).select(range(1000)) # Only use 1000 samples for quick demo

def format_chat_template(row):
    row_json = [{"role": "system", "content": row["instruction"]},
               {"role": "user", "content": row["input"]},
               {"role": "assistant", "content": row["output"]}]
    row["text"] = tokenizer.apply_chat_template(row_json, tokenize=False)
    return row

dataset = dataset.map(
    format_chat_template,
    num_proc= 4,
)

dataset

输出: 

Dataset({
    features: ['instruction', 'input', 'output', 'text'],
    num_rows: 1000
})

让我们查看第 3 行的 “text” 列。 

dataset['text'][3]

“text” 列包含以 OpenAI 风格组织的指令、病人问题与医生回复。

Displaying the text column of the Health care dataset.

用于模型评估,我们将把数据集拆分为训练集与测试集。 

dataset = dataset.train_test_split(test_size=0.1)

5. 配置并训练模型

现在我们设置训练参数与 SFT 参数,然后启动训练过程。 

您可以根据自己的环境、算力与内存调整各类超参数。以下超参数针对 Kaggle Notebook 做了优化。如果要在 Google Colab 上运行,请考虑尝试不同的训练策略。 

# Setting Hyperparamter
training_arguments = TrainingArguments(
    output_dir=new_model,
    per_device_train_batch_size=1,
    per_device_eval_batch_size=1,
    gradient_accumulation_steps=2,
    optim="paged_adamw_32bit",
    num_train_epochs=1,
    eval_strategy="steps",
    eval_steps=0.2,
    logging_steps=1,
    warmup_steps=10,
    logging_strategy="steps",
    learning_rate=2e-4,
    fp16=False,
    bf16=False,
    group_by_length=True,
    report_to="wandb"
)
# Setting sft parameters
trainer = SFTTrainer(
    model=model,
    train_dataset=dataset["train"],
    eval_dataset=dataset["test"],
    peft_config=peft_config,
    max_seq_length= 512,
    dataset_text_field="text",
    tokenizer=tokenizer,
    args=training_arguments,
    packing= False,
)

model.config.use_cache = False
trainer.train()

微调耗时将近 25 分钟。可以看到训练与验证损失在逐步降低。若需更好表现,建议在完整数据集上至少训练三个 epoch。

Model evaluation with training and validation loss.微调对初学者与非技术人员可能略显复杂。若您想要更简单的方案,建议查看 Fine-Tuning OpenAI's GPT-4: A Step-by-Step Guide 教程。它介绍了如何使用 OpenAI API 以简便方式微调最先进的模型。

6. 模型评估

结束本次 Weights and Biases 实验,将生成评估报告。 

wandb.finish()
model.config.use_cache = True

W&B run history and run summary

要查看详细报告,请登录您的 Weights and Biases 账户,点击 “Fine-tune Gemma-2-9b-it on Healthcare Dataset” 的项目名称。

W&B run summary on the W&B website.

截图来自 wandb.ai

7. 保存更改

我们现在将把微调后的适配器保存在本地,并推送至 Hugging Face Hub。 

trainer.model.save_pretrained(new_model)
trainer.model.push_to_hub(new_model, use_temp_dir=False)

若要在另一份 Kaggle 笔记本中使用已保存的适配器,需要先保存该笔记本。点击右上角 “Save Version”,选择 “Quick Save” 与 “Always save output when creating a Quick Save”,然后点击 “Save”。 

Quick saving the changes mad in the Kaggle notebook.

如果您在运行代码时遇到问题,请克隆 Kaggle Notebook 并直接运行。您需要通过 Kaggle Secrets 配置 Hugging Face 与 Weights & Biases 的 API 密钥。

若要判断针对您的具体场景是微调更合适还是检索增强生成(RAG)更合适,强烈推荐阅读 RAG vs Fine-Tuning 博文。 

将基座模型与适配器合并

现在我们将把适配器与基座模型合并,并把完整模型推送到 Hugging Face Hub。

创建一个新的 Kaggle 笔记本,选择 CPU 作为加速器,并安装必要的 Python 包。

为什么使用 CPU 作为加速器?难道不会很慢吗?会的,但 Kaggle 的 GPU 机器只提供 16GB 显存,足以加载 70 亿参数模型,却不够 90 亿。而 CPU 机器提供 30GB 内存,足以同时加载基座模型与适配器。

%%capture
%pip install -U bitsandbytes
%pip install -U transformers
%pip install -U accelerate
%pip install -U peft
%pip install -U trl

从 Kaggle Secrets 获取 API 密钥并登录 Hugging Face CLI。 

from huggingface_hub import login
from kaggle_secrets import UserSecretsClient
user_secrets = UserSecretsClient()

hf_token = user_secrets.get_secret("HUGGINGFACE_TOKEN")
login(token = hf_token)

要访问保存的模型,您需要导入已保存的 Kaggle 笔记本。点击右上角 “Add Input”,选择 “Your Work” 选项卡,然后点击已保存笔记本的加号按钮即可访问其中所有文件。 

Adding the saved notebook as an input.

通过提供 Hugging Face 仓库名称设置基座模型 URL;同时通过指定适配器本地目录来配置适配器 URL。

base_model_url = "google/gemma-2-9b-it"
new_model_url = "/kaggle/input/fine-tune-gemma-2-9b-it-on-healthcare-dataset/Gemma-2-9b-it-chat-doctor/"

使用基座模型 URL 加载分词器与完整模型。请确保将 device 设为 “cpu”,dtype 设为 “float16”。

from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig, pipeline
from peft import PeftModel
import torch
from trl import setup_chat_format


# Reload tokenizer and model
tokenizer = AutoTokenizer.from_pretrained(base_model_url)

base_model_reload= AutoModelForCausalLM.from_pretrained(
    base_model_url,
    low_cpu_mem_usage=True,
    return_dict=True,
    torch_dtype=torch.float16,
    device_map="cpu",
)

为新加载的基座模型设置聊天格式,并与适配器结合。最后,我们将加载并把适配器合并到基座模型中。 

merge_and_unload() 函数将帮助我们把适配器权重与基座模型合并,使其成为独立模型。

base_model_reload, tokenizer = setup_chat_format(base_model_reload, tokenizer)
model = PeftModel.from_pretrained(base_model_reload, new_model_url)

model = model.merge_and_unload()

将完整模型与分词器保存在本地。

model.save_pretrained("Gemma-2-9b-it-chat-doctor")
tokenizer.save_pretrained("Gemma-2-9b-it-chat-doctor")

同时,将所有模型文件与分词器推送至 Hugging Face Hub。 

model.push_to_hub("Gemma-2-9b-it-chat-doctor", use_temp_dir=False)
tokenizer.push_to_hub("Gemma-2-9b-it-chat-doctor", use_temp_dir=False)

您可以前往自己的 Hugging Face 模型仓库查看所有文件。 

Finetuned model repository on Hugging Face.

来源: kingabzpro/Gemma-2-9b-it-chat-doctor · Hugging Face

如果您在使用 CPU 合并模型时遇到问题,请参考 Kaggle Notebook。

使用 Hugging Face Space 进行量化

借助 GGUF My Repo 这个 Hugging Face Space,模型转换与量化变得简单且快速。您只需登录并提供模型 ID 即可。 

来源: GGUF My Repo - a Hugging Face Space by ggml-org

它将为您创建一个新的模型仓库,内含量化后的模型文件,您之后可下载并在本地使用。就这么简单。 

想要了解量化背后的理论,请阅读我们的文章:Quantization for Large Language Models (LLMs): Reduce AI Model Sizes Efficiently。

fine-tuned quantized model repository on hugging face

来源: kingabzpro/Gemma-2-9b-it-chat-doctor-Q4_K_M-GGUF · Hugging Face

但如果您想亲自上手,在本地运行 llama.cpp 脚本,那么可查看 Fine-Tuning Llama 3 and Using It Locally 教程。

在本地使用微调模型

要在本地使用量化模型:

  1. 首先从官网下载并安装 Jan 应用。 
  2. 启动应用并进入 “Model Hub”。点击左上角 Windows “🪟” 按钮可打开菜单。 
  3. 将微调模型仓库链接粘贴到搜索栏并回车:"kingabzpro/Gemma-2-9b-it-chat-doctor-Q4_K_M-GGUF"

importing the model in Jan app using the model URL

  1. 将会跳转至新菜单,您可以直接选择并下载模型。 

selecting and downloading the GGUF file.

  1. 进入 “Thread” 菜单,在右上角 “Model” 选项卡中选择微调模型。  

selecting the fine-tuned model

  1. 开始向 AI 助手提问,获取定制化回答。 

running the model with out configuration

  1. 可以看到,初始回复质量一般。我们需要调整一些模型参数。前往 “Model” 选项卡,开始调整如下参数:
  • Stop: <endofstring>, Best wishes, Chat Doctor.
  • Frequency Penalty: 0.5
  • Max Tokens:  680
  1. 进入 “Assistant” 选项卡,将 Instructions 修改为 “If you are a doctor, please answer the medical questions based on the patient's description.”
  2. 用多个问题测试模型,体验高质量、直截了当的结果。

running the model with configuration

总结

Gemma 2 是非常出色的模型;如果配合 Keras 3 框架进行分布式微调与推理,将更上一层楼。与使用 Transformers 框架相比,您会获得更快的训练与推理速度。 

您可以通过教程 Fine-Tune and Run Inference on Google's Gemma Model with TPU 学习如何使用 Keras 3 微调 Gemma 模型并进行推理。

在本教程中,我们了解了 Gemma 2 模型,以及如何通过 Transformers 库访问它。我们还学习了如何在病患-医生对话上微调模型,将适配器与基座模型合并、将完整模型推送至 Hugging Face Hub,使用 Hugging Face Space 对模型进行转换与量化,并通过 Jan 应用将模型下载到笔记本电脑上本地运行。 

这是一项有趣的项目,值得 AI 爱好者尝试。它将提升您在大型语言模型微调过程中排查问题的能力。主要挑战通常涉及内存与算力限制,以及如何降低损失。

您还可以在 DataCamp 的 Developing Large Language Models 课程中,学习如何用 PyTorch 与 Hugging Face 开发自有 LLM。

Abid Ali Awan

作者

Abid Ali Awan

作为一名持证的数据科学家,我热衷于利用前沿技术打造创新的机器学习应用。凭借在语音识别、数据分析与报告、MLOps、对话式人工智能以及自然语言处理方面的扎实背景,我不断打磨构建智能系统的能力,力求带来切实影响。除技术专长外,我也擅长沟通,能够将复杂概念提炼为清晰、简明的表述。因此,我成为数据科学领域备受关注的博主,与不断壮大的数据专业人士社区分享洞见与实践经验。目前,我专注于内容创作与编辑,借助大语言模型打造有力且吸引人的内容,帮助企业与个人更好地发挥数据价值。

主题