本地部署大模型以及微调手册

本地部署大模型以及微调手册

——by ChatGPT

概述

本手册旨在指导读者在本地(如个人电脑、工作站或私有服务器)环境下部署与微调(Fine-tune)大型语言模型(LLM)。内容涵盖硬件与软件环境准备、模型获取与加载、推理优化、微调方法(包括 LoRA、P-tuning、全参微调等)、训练流程示例、常见问题及最佳实践等。以下内容基于 Hugging Face Transformers、PyTorch 生态,以及常见开源大模型(如 GPT-2、LLaMA、Mistral、Bloom 等)的公开或授权使用情况编写。适用读者应具备基本的 Linux/Windows 命令行操作、Python 编程、深度学习基础知识。


目录

  1. 前置条件
    1.1 硬件要求
    1.2 软件与环境依赖
    1.3 概念术语说明
  2. 环境准备
    2.1 操作系统与基础依赖安装
    2.2 Python 环境与包管理
    2.3 CUDA、cuDNN、NCCL 安装与验证
    2.4 Conda/venv 虚拟环境配置示例
  3. 模型获取与部署
    3.1 Hugging Face Transformers 概览
    3.2 下载与加载预训练模型
    3.3 模型量化(Quantization)与显存优化
    3.4 推理示例代码
    3.5 使用 DeepSpeed/In-8bit 推理加速
  4. 微调策略与实现
    4.1 微调方法概述
    4.2 全参数微调(Full Fine-tuning)
    4.3 LoRA(Low-Rank Adaptation)
    4.4 P-tuning / Prompt Tuning
    4.5 PEFT(Parameter-Efficient Fine-Tuning)架构介绍
    4.6 数据集准备与预处理
  5. 实战示例:使用 LoRA 微调 LLaMA-7B
    5.1 数据集选取与格式化
    5.2 PEFT + Transformers 脚本示例
    5.3 训练超参数与分布式训练配置
    5.4 检查点保存与模型导出
    5.5 微调后模型推理对比
  6. 推理部署优化
    6.1 8-bit/4-bit 量化推理
    6.2 ONNX 转换与加速
    6.3 TensorRT/TVM 简述
    6.4 多 GPU / 多机部署示例
  7. 常见问题与调优建议
    7.1 显存不足解决思路
    7.2 学习率、Batch Size 调试
    7.3 收敛慢或过拟合应对
    7.4 推理速度瓶颈分析
  8. 附录
    8.1 常用平台与工具链接
    8.2 参考文献与官方文档

1. 前置条件

1.1 硬件要求

  • GPU
    • 推荐:NVIDIA A100/RTX 3090/RTX 4090 等显存 ≥24GB 的显卡
    • 若显存不足,可使用 8-bit/4-bit 量化、梯度累积等策略
  • CPU
    • 至少 8 核(推荐 16 核以上)
    • 多线程 I/O 时更高核数更有利
  • 内存(RAM)
    • ≥32GB,若要做大规模数据预处理或多卡训练,建议 ≥64GB
  • 存储
    • ≥500GB 可用空间
    • SSD 优先,数据读取与模型权重加载更快

1.2 软件与环境依赖

  • 操作系统:Ubuntu 20.04 / 22.04、Debian、CentOS 7/8,或 Windows 10/11(建议使用 WSL2 + Ubuntu)
  • CUDA Toolkit:11.7 及以上(与 PyTorch 兼容,后续示例以 CUDA 11.7 为例)
  • cuDNN:8.x,与对应 CUDA 版本匹配
  • Python 版本:3.8 - 3.10(由于 Hugging Face Transformers 和相关库的兼容性)
  • PyTorch:1.13 或更高
  • Transformers:4.28 或更高
  • Accelerate:0.18 或更高(用于多卡/分布式训练)
  • PEFT:0.3.0 或更高(用于 LoRA/P-tuning)
  • bitsandbytes:0.39.0 或更高(用于 8-bit/4-bit 量化)
  • datasets:2.x(用于数据集加载与预处理)
  • tokenizers:0.13.0 或更高
  • 其他依赖:numpy、pandas、scikit-learn、tqdm 等

1.3 概念术语说明

  • Pre-trained Model(预训练模型):在海量文本语料上训练得到的通用语言模型权重,可用于下游任务微调或推理。
  • Fine-tuning(微调):在预训练基础上,针对特定任务或数据集,继续训练模型以获得更佳效果的过程。
  • LoRA(Low-Rank Adaptation):一种只微调低秩矩阵(而非整个模型参数)的技术,大幅降低显存与训练成本。
  • P-tuning / Prompt Tuning:通过学习可微调的“软提示”(soft prompt),仅修改输入而非模型权重,实现参数高效微调。
  • Quantization(量化):将浮点数参数(如 FP32)转换为更低精度(如 INT8、INT4),在保证精度损失可控的前提下减少显存占用及加速推理。
  • Accelerate:Hugging Face 推出的分布式训练工具,简化多 GPU / 多机环境下的模型训练与推理。
  • PEFT(Parameter-Efficient Fine-Tuning):统一封装 LoRA、P-tuning 等轻量级微调方法的库,便于快速实验对比与部署。

2. 环境准备

2.1 操作系统与基础依赖安装

以下以 Ubuntu 22.04 为示例,其他发行版可根据包管理器适当调整。

1
2
3
4
5
6
7
8
# 更新系统
sudo apt update && sudo apt upgrade -y

# 安装常用工具
sudo apt install -y build-essential git wget curl unzip zip

# 安装 Python 依赖 (建议使用 apt 安装 Python3、pip3,并利用 venv/conda 管理环境)
sudo apt install -y python3 python3-pip python3-venv

2.2 Python 环境与包管理

推荐使用 Conda 或 venv 创建隔离环境。以下示例展示 Conda 用法,若使用 venv,对应将 conda 替换为 python3 -m venv 即可。

1
2
3
4
5
6
# 创建并激活 conda 环境
conda create -n llm_env python=3.9 -y
conda activate llm_env

# 升级 pip
pip install --upgrade pip

2.3 CUDA、cuDNN、NCCL 安装与验证

  1. 安装 CUDA Toolkit 11.7

    • 前往 NVIDIA 官方网站下载相应 .run 或通过包管理器安装。示例通过 apt:

      1
      2
      3
      4
      5
      6
      7
      # 添加 NVIDIA 源
      sudo apt-key adv --fetch-keys https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/7fa2af80.pub
      sudo sh -c 'echo "deb https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/ /" > /etc/apt/sources.list.d/cuda.list'
      sudo apt update

      # 安装 CUDA 11.7(建议根据官网最新包名调整)
      sudo apt install -y cuda-11-7
    • 安装完成后,将以下两行添加到 ~/.bashrc~/.zshrc

      1
      2
      export PATH=/usr/local/cuda-11.7/bin:$PATH
      export LD_LIBRARY_PATH=/usr/local/cuda-11.7/lib64:$LD_LIBRARY_PATH
    • 重新加载配置:source ~/.bashrc

  2. 安装 cuDNN

    • 前往 NVIDIA Developer 账号下载对应 CUDA 版本的 cuDNN 包(如 cuDNN 8.5 for CUDA 11.x)。

    • 解压并复制库文件。例如:

      1
      2
      3
      4
      tar -xzvf cudnn-linux-x86_64-*.tgz
      sudo cp cuda/include/cudnn*.h /usr/local/cuda/include
      sudo cp cuda/lib64/libcudnn* /usr/local/cuda/lib64
      sudo chmod a+r /usr/local/cuda/include/cudnn*.h /usr/local/cuda/lib64/libcudnn*
  3. 安装 NCCL(可选,多机/多卡通信加速)

    • 如果需要多卡或多机训练,建议安装 NCCL。可通过包管理器或从 GitHub Release 下载预编译包。

    • 也可使用 Conda 安装 NCCL:

      1
      conda install -c nvidia nccl -y
  4. 验证 GPU 环境

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    # 检查 NVIDIA 驱动
    nvidia-smi

    # 检查 CUDA 版本
    nvcc -V

    # 在 Python 中验证 PyTorch 可用 GPU
    python - <<EOF
    import torch
    print("CUDA available:", torch.cuda.is_available())
    print("CUDA device count:", torch.cuda.device_count())
    print("CUDA device name:", torch.cuda.get_device_name(0))
    EOF

2.4 Conda/venv 虚拟环境配置示例

以 Conda 为例安装主要 Python 包,确保与 GPU 兼容的 PyTorch 版本:

1
2
3
4
5
6
7
8
9
10
11
12
# 安装 PyTorch + CUDA 支持
# 参照 https://pytorch.org/get-started/locally/ 查询最新命令,例如:
conda install pytorch torchvision torchaudio pytorch-cuda=11.7 -c pytorch -c nvidia -y

# 安装 Hugging Face Transformers、Accelerate、PEFT、datasets 等
pip install transformers accelerate peft datasets tokenizers

# 安装 bitsandbytes(支持 8-bit/4-bit 量化)
pip install bitsandbytes

# 安装其他常用库
pip install numpy pandas scikit-learn tqdm

3. 模型获取与部署

3.1 Hugging Face Transformers 概览

Hugging Face Transformers 是当前社区最主流的预训练语言模型框架,支持大量开源模型(如 GPT、BERT、LLaMA、Bloom 等)。其核心设计可分为:

  • 模型结构与权重transformers.AutoModelForCausalLMAutoTokenizer
  • 加速器与推理优化transformers.pipelinebitsandbytes 量化、DeepSpeed
  • 训练 / 微调接口Trainer API、Accelerate 分布式训练、PEFT 轻量化微调)

在本地部署大模型时,常见做法为:

  1. 从 Hugging Face Hub 下载权重。若使用商业模型(如 LLaMA 7B),需自行申请许可并手动上传到本地或私有 Hugging Face Space。
  2. 选择合适的设备加载。单卡 GPU 可直接加载,若显存不足需结合 8-bit/4-bit 量化或使用 CPU+GPU 混合。
  3. 编写推理脚本,使用 model.generate 生成文本。

3.2 下载与加载预训练模型

以下示例以 LLaMA-7B(假设已获得授权并放置在本地目录 ./llama-7b/)为例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
from transformers import AutoTokenizer, AutoModelForCausalLM
import torch

# 若使用本地权重,指定本地路径
model_name_or_path = "./llama-7b/"

# 加载分词器与模型
tokenizer = AutoTokenizer.from_pretrained(model_name_or_path, use_fast=False)
model = AutoModelForCausalLM.from_pretrained(
model_name_or_path,
torch_dtype=torch.float16, # 若显存充足可使用半精度
low_cpu_mem_usage=True # 减少加载时 CPU 内存占用
)

# 将模型移动到 GPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)

# 推理示例
prompt = "以下是一道数学题:\n请计算 345 × 678 等于多少?\n答案:"
inputs = tokenizer(prompt, return_tensors="pt").to(device)
outputs = model.generate(
**inputs,
max_new_tokens=50,
temperature=0.7,
top_p=0.9,
do_sample=True
)
print(tokenizer.decode(outputs[0], skip_special_tokens=True))

若直接从 Hugging Face Hub 下载通用模型(如 gpt2-mediumEleutherAI/gpt-j-6B 等):

1
2
3
# 下载 gpt-j-6B
git lfs install # 确保 git-lfs 已安装并配置
git clone https://huggingface.co/EleutherAI/gpt-j-6B ./gpt-j-6B

然后与上述代码相同,直接将 model_name_or_path="./gpt-j-6B"

3.3 模型量化与显存优化

在本地单卡显存有限(如 16GB 或 24GB)的情况下,可使用 bitsandbytes 将模型加载为 8-bit 或 4-bit,以节省显存并加速推理。

安装前确保系统支持:

1
pip install bitsandbytes

8-bit 加载示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from transformers import AutoTokenizer, AutoModelForCausalLM
import bitsandbytes as bnb
import torch

model_name_or_path = "EleutherAI/gpt-j-6B"

tokenizer = AutoTokenizer.from_pretrained(model_name_or_path)
model = AutoModelForCausalLM.from_pretrained(
model_name_or_path,
load_in_8bit=True, # 关键参数:8-bit 加载
device_map="auto", # 自动分配设备(多卡时可自动拆分)
)

# 推理
input_ids = tokenizer("你好,世界!", return_tensors="pt").input_ids.to(model.device)
out = model.generate(input_ids, max_new_tokens=50)
print(tokenizer.decode(out[0], skip_special_tokens=True))

4-bit 加载示例(需较新版本的 Transformers + bitsandbytes):

1
2
3
4
5
6
7
8
9
model = AutoModelForCausalLM.from_pretrained(
model_name_or_path,
load_in_4bit=True,
device_map="auto",
quantization_config=bnb.quantization.QuantizationConfig(
load_in_4bit=True,
llm_int8_threshold=6.0 # 可调阈值
)
)

注意:4-bit 量化相较 8-bit 会带来更大的显存节省,但生成质量可能略有下降,需要在阈值与评估指标上进行调优。

3.4 推理示例代码

以下示例展示如何使用 Hugging Face pipeline 简化推理流程(以 GPT-2 为例):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from transformers import pipeline

# 创建文本生成 pipeline
generator = pipeline(
"text-generation",
model="gpt2-medium",
tokenizer="gpt2-medium",
device=0 # 使用 GPU 0;若无 GPU,可删除该参数
)

# 生成文本
prompt = "从前有一只小狐狸,"
results = generator(
prompt,
max_length=100,
do_sample=True,
temperature=0.8,
top_p=0.95,
num_return_sequences=3
)

for i, res in enumerate(results):
print(f"=== 生成结果 {i+1} ===")
print(res["generated_text"])
print()

如果需要自定义调度(如逐步移除注意力层缓存、强制禁用缓存以减少显存峰值等),可手动编写类似前节 3.2 的 model.generate 代码。

3.5 使用 DeepSpeed / In-8bit 推理加速

  • DeepSpeed-Inference:适用于超大模型(如 30B+),可启用 ZeRO-Inference / 8-bit 量化 / CPU-Offload 等功能。

  • 配置示例

    1
    pip install deepspeed

    然后在脚本中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    from transformers import AutoTokenizer, AutoModelForCausalLM
    import torch

    model_name_or_path = "facebook/opt-30b"

    tokenizer = AutoTokenizer.from_pretrained(model_name_or_path)
    model = AutoModelForCausalLM.from_pretrained(
    model_name_or_path,
    device_map="auto",
    torch_dtype=torch.float16,
    low_cpu_mem_usage=True
    )

    # 使用 DeepSpeed 推理加速
    from transformers import pipeline
    generator = pipeline(
    "text-generation",
    model=model,
    tokenizer=tokenizer,
    device=0,
    config={"use_deepspeed": True}
    )

    output = generator("今天天气如何?", max_new_tokens=50)
    print(output)
  • DeepSpeed 也支持自定义推理配置文件(JSON),可进一步优化 GPU/CPU 资源分配。


4. 微调策略与实现

在本地环境下,直接对大模型(数十亿参数)做全参数微调往往会面临显存、计算量和时间成本过高的问题。因此,常用以下几种参数高效微调方法(PEFT):

  1. 全参数微调(Full Fine-tuning)
    • 直接微调全部模型参数
    • 优点:最灵活,适用性最广
    • 缺点:参数量大(数十亿)、显存/计算量需求高、不便于模型部署
  2. LoRA(Low-Rank Adaptation)
    • 仅在注意力层的权重矩阵上添加低秩矩阵(A、B 两个矩阵),只训练这部分权重
    • 大幅减少可训练参数(通常只需 1~2 亿参数),显存占用显著降低
    • 微调完成后保存 LoRA 权重,与基模型共享使用即可
  3. P-tuning / Prompt Tuning
    • 在模型输入前加入可训练的“虚拟 token”(soft prompt)
    • 不修改模型权重,仅学习输入的 prompt 表示,参数量小(通常几百万)
    • 适合需要少量样本快速微调的场景
  4. Adapter-based 微调
    • 类似 LoRA,但在每个 Transformer 层中添加适配器(小型 MLP)
    • 训练时只更新适配器参数
  5. PEFT(Parameter-Efficient Fine-Tuning)
    • Hugging Face 提供的统一库,封装上述 LoRA、P-tuning、Adapter 等多种微调方法
    • 方便多种方法对比、切换

本节将重点介绍 LoRA + PEFT,因为它兼顾效果与效率,且社区资料丰富。

4.1 微调方法概述

4.1.1 全参数微调(Full Fine-tuning)

  • 步骤

    1. 加载预训练模型
    2. 添加 task-specific 层(如分类头、多任务头等,若需要)
    3. 使用下游数据进行训练(通常使用 AdamW 优化器)
    4. 保存整个微调后的模型
  • 显存/计算需求

    • 7B 参数模型至少需要 40GB 显存才能以 FP16 运行单卡训练
    • 需要大量训练时间(数十万 ~ 一百万步)才能充分收敛
  • 示例代码(针对文本生成任务):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    from transformers import AutoTokenizer, AutoModelForCausalLM, Trainer, TrainingArguments
    from datasets import load_dataset

    model_name = "EleutherAI/gpt-j-6B"
    tokenizer = AutoTokenizer.from_pretrained(model_name)
    model = AutoModelForCausalLM.from_pretrained(model_name, torch_dtype=torch.float16)

    # 加载训练数据示例(这里以 wikitext-2 为例)
    dataset = load_dataset("wikitext", "wikitext-2-raw-v1", split="train")

    def tokenize_fn(examples):
    return tokenizer(examples["text"], truncation=True, max_length=512)

    tokenized = dataset.map(tokenize_fn, batched=True, remove_columns=["text"])
    tokenized.set_format(type="torch", columns=["input_ids"])

    training_args = TrainingArguments(
    output_dir="./ft_gptj",
    per_device_train_batch_size=1,
    gradient_accumulation_steps=8,
    fp16=True,
    num_train_epochs=1,
    learning_rate=1e-5,
    logging_steps=100,
    save_steps=500,
    save_total_limit=2,
    remove_unused_columns=True,
    dataloader_num_workers=4,
    )

    trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=tokenized,
    tokenizer=tokenizer,
    )

    trainer.train()
    trainer.save_model("./ft_gptj_final")

4.1.2 LoRA(Low-Rank Adaptation)

  • 原理:在注意力层的查询(Q)和键(K)矩阵等位置插入两个低秩矩阵 A(降维)与 B(升维),只微调 A、B 的参数。

  • 优点

    • 大幅减少可训练参数(通常只需基模型参数的 1% ~ 5%)
    • 显存占用降低,可在单卡 24GB 上微调 7B、13B 模型
    • 微调后可将 LoRA 参数与基模型分离,轻量化部署
  • 关键参数

    • r(rank):低秩矩阵的秩,决定投影维度(如 r=8,代表 A: d×8,B: 8×d)
    • alpha:缩放系数,常用值与 r 共同调节最终更新幅度
    • target_modules:指定要插入 LoRA 的层名称列表,如 ["q_proj", "v_proj"]
  • 示例代码(针对 LLaMA-7B):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    from transformers import LlamaForCausalLM, LlamaTokenizer
    from peft import get_peft_model, LoraConfig, TaskType
    import torch

    model_name = "./llama-7b/"
    tokenizer = LlamaTokenizer.from_pretrained(model_name)
    model = LlamaForCausalLM.from_pretrained(
    model_name,
    torch_dtype=torch.float16,
    device_map="auto",
    )

    # 配置 LoRA 参数
    peft_config = LoraConfig(
    task_type=TaskType.CAUSAL_LM,
    inference_mode=False,
    r=16,
    lora_alpha=32,
    lora_dropout=0.05,
    target_modules=["q_proj", "k_proj", "v_proj"],
    )
    model = get_peft_model(model, peft_config)

    # 准备数据(同 5.1 小节示例)
    # ...

    # 定义 Trainer
    from transformers import Trainer, TrainingArguments
    from datasets import load_dataset

    dataset = load_dataset("json", data_files={"train": "train_data.json"})["train"]
    def tokenize_fn(examples):
    # 示例:将输入拼接为 prompt->response 形式
    inputs = [f"### 指令:{item['instruction']}\n### 上下文:{item.get('context','')}\n### 回答:" for item in examples]
    outputs = [item["output"] for item in examples]
    model_inputs = tokenizer(inputs, max_length=512, truncation=True, padding="max_length")
    labels = tokenizer(outputs, max_length=256, truncation=True, padding="max_length")
    model_inputs["labels"] = labels["input_ids"]
    return model_inputs

    tokenized_dataset = dataset.map(tokenize_fn, batched=True, remove_columns=dataset.column_names)
    tokenized_dataset.set_format(type="torch", columns=["input_ids", "attention_mask", "labels"])

    training_args = TrainingArguments(
    output_dir="./lora_llama7b",
    per_device_train_batch_size=1,
    gradient_accumulation_steps=4,
    num_train_epochs=3,
    learning_rate=3e-4,
    fp16=True,
    logging_steps=50,
    save_steps=200,
    save_total_limit=3,
    report_to="none", # 如果不使用 wandb 等日志服务
    )

    trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=tokenized_dataset,
    data_collator=lambda data: {
    "input_ids": torch.stack([f["input_ids"] for f in data]),
    "attention_mask": torch.stack([f["attention_mask"] for f in data]),
    "labels": torch.stack([f["labels"] for f in data]),
    },
    )

    trainer.train()
    # 保存 LoRA 微调结果
    model.save_pretrained("./lora_llama7b_final")
    tokenizer.save_pretrained("./lora_llama7b_final")

4.1.3 P-tuning / Prompt Tuning

  • 原理:在输入序列前添加可学习的“虚拟 token”表示,仅优化这些 prompt 向量,不修改模型参数。
  • 适用:下游任务数据稀缺或仅想尝试少量超参
  • 限制:效果通常略低于 LoRA,全参微调
  • 示例代码
    Hugging Face PEFT 中对 P-tuning 的支持还在不断完善,可参考官方示例:https://github.com/huggingface/peft

4.2 全参数微调(Full Fine-tuning)详解

详见 4.1.1。若显存充足,可直接使用 Trainer API,或编写自定义 Accelerator 脚本完成更灵活的训练。

4.3 LoRA 实现要点

  1. 选择插入位置
    • 对 Transformer 中的 Query、Key、Value、Output 等矩阵插入 LoRA。对于 LLaMA,可从源代码或模型 config 查找注意力层模块名。
  2. 冻结原始权重
    • get_peft_model 会自动冻结基模型参数,只保留 LoRA 参数可训练。
  3. 数据格式与标签对齐
    • 文本生成任务中,往往需要将输入和标签拼接在同一序列,并在 labels 中指定只计算输出部分 loss。
    • 可通过 tokenizer.pad_token_id = tokenizer.eos_token_id 处理填充 token。
  4. 训练细节
    • 通常建议使用较大学习率(如 1e-4 ~ 3e-4),并配合梯度累积。
    • fp16=Truegradient_checkpointing=True 可进一步节省显存。

4.4 P-tuning / Prompt Tuning 详解

  • 添加虚拟 token
    1. 定义一个可学习的 embedding 矩阵,大小为 [num_virtual_tokens, hidden_size]
    2. 每次向模型前向时,将这段 embedding 与真实 token embedding 拼接,形成新的输入。
    3. 仅对这段虚拟 embedding 参数进行梯度更新。
  • 框架
    • 目前 Hugging Face PEFT 对 P-tuning 的支持需搭配 transformers.Trainer 做自定义改写,或参考官方教程。

4.5 PEFT(Parameter-Efficient Fine-Tuning)架构介绍

PEFT 库统一了 LoRA、P-tuning、Adapter 等方法,提供以下核心接口:

  • get_peft_model(model, config):将基模型与 PEFT 配置结合,返回可训练的 PEFT 模型。
  • peft_config = LoraConfig(...):定义 LoRA 相关超参。
  • peft_config = PromptTuningConfig(...):定义 P-tuning 相关超参。
  • model.print_trainable_parameters():查看可训练参数与冻结参数占比。

通过 PEFT,可以轻松切换不同微调方法,对比效果,且微调完成后仅保存少量权重,便于线上部署。

4.6 数据集准备与预处理

  1. 选择数据格式
    • 文本分类:CSV/JSON,字段包含 input_textlabel
    • 文本生成:JSON 格式,每条记录包含 instructioninput(可选)与 output
    • 问答:SQuAD 格式 JSON;
  2. 数据清洗
    • 去除空行、非法字符;
    • 统一编码为 UTF-8;
    • 字段对齐、检查空值或格式错误;
  3. 分词与缓存
    • 使用 datasets 库的 map 方法将文本转换为 input_idsattention_masklabels 等;
    • 如果 GPU 显存不足,可先在 CPU 上完成预处理并将结果缓存到磁盘,以加快每次实验启动速度。
  4. 构造 DataLoader
    • 对于自定义脚本:手动使用 torch.utils.data.DatasetDataLoader
    • 对于 Trainer:将 datasets.Dataset 传入,并配置 data_collator

示例:构建简单的“指令-回复”训练集:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from datasets import Dataset
import json

# 假设 raw_data.json 格式为:[{ "instruction": "...", "input": "...", "output": "..." }, ...]
with open("raw_data.json", "r", encoding="utf-8") as f:
records = json.load(f)

def format_example(ex):
prompt = f"### 指令:{ex['instruction']}\n"
if ex.get("input"):
prompt += f"### 上下文:{ex['input']}\n"
prompt += "### 回答:"
return {"prompt": prompt, "output": ex["output"]}

formatted = [format_example(rec) for rec in records]
dataset = Dataset.from_list(formatted)

5. 实战示例:使用 LoRA 微调 LLaMA-7B

下面将从头到尾演示如何在单卡 24GB 显存(或多卡环境)下,使用 LoRA 微调 LLaMA-7B,以一个“指令-回复”问答场景为例。

5.1 数据集选取与格式化

假设我们有一个自定义的 JSON 格式数据集 train_data.json,每条记录包含:

1
2
3
4
5
6
7
8
9
10
11
12
13
[
{
"instruction": "请介绍一下长江的地理位置。",
"input": "",
"output": "长江,位于中国中部和东部,是世界第三长河,全长约6300公里。发源于青海省唐古拉山脉,流经多省市,最后注入东海。"
},
{
"instruction": "如何使用 Python 读取一个 CSV 文件?",
"input": "",
"output": "可以使用 pandas 库:\n```python\nimport pandas as pd\ndf = pd.read_csv('file.csv')\n```"
}
// … 其他样本 …
]

格式化逻辑:将每条记录转换为如下两部分:

  • prompt

    1
    2
    3
    ### 指令:<instruction>
    ### 上下文:<input> # 如果 input 为空,可省略该行
    ### 回答:
  • response:即 output 字段

将其保存为 Hugging Face datasets.Dataset,并在 map 函数中进行分词。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
from datasets import load_dataset
from transformers import LlamaTokenizer

# 加载原始 JSON 数据
dataset = load_dataset("json", data_files={"train": "train_data.json"})["train"]

# 加载分词器
tokenizer = LlamaTokenizer.from_pretrained("./llama-7b/")

# 若 tokenizer 缺少 pad_token,设为 eos_token
if tokenizer.pad_token_id is None:
tokenizer.pad_token_id = tokenizer.eos_token_id

def preprocess_fn(examples):
prompts, labels = [], []
for inst, inp, out in zip(examples["instruction"], examples["input"], examples["output"]):
prompt = f"### 指令:{inst}\n"
if inp and inp.strip() != "":
prompt += f"### 上下文:{inp}\n"
prompt += "### 回答:"
prompts.append(prompt)
labels.append(out)

# 合并 prompt 与 labels,便于一次性编码
input_encodings = tokenizer(prompts, padding="max_length", truncation=True, max_length=512)
label_encodings = tokenizer(labels, padding="max_length", truncation=True, max_length=256)

# 将未使用的部分设为 -100,以忽略 loss 计算
input_ids = input_encodings["input_ids"]
attention_mask = input_encodings["attention_mask"]
labels_ids = label_encodings["input_ids"]
for i in range(len(labels_ids)):
# 将 prompt 部分的 label 标记为 -100
prompt_len = len(tokenizer(prompts[i], return_tensors="pt").input_ids[0])
labels_ids[i][:prompt_len] = [-100] * prompt_len

return {
"input_ids": input_ids,
"attention_mask": attention_mask,
"labels": labels_ids
}

# 预处理并缓存
tokenized_dataset = dataset.map(
preprocess_fn,
batched=True,
remove_columns=dataset.column_names
)
tokenized_dataset.set_format(type="torch", columns=["input_ids", "attention_mask", "labels"])

5.2 PEFT + Transformers 微调脚本示例

将上节数据处理与 LoRA 配置结合,示例脚本如下保存为 finetune_lora_llama7b.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
import os
import torch
from transformers import LlamaForCausalLM, LlamaTokenizer, Trainer, TrainingArguments
from peft import LoraConfig, get_peft_model, TaskType
from datasets import load_dataset

def main():
model_name = "./llama-7b/"
output_dir = "./lora_llama7b_finetuned/"

# 加载分词器与模型
tokenizer = LlamaTokenizer.from_pretrained(model_name)
if tokenizer.pad_token_id is None:
tokenizer.pad_token_id = tokenizer.eos_token_id

model = LlamaForCausalLM.from_pretrained(
model_name,
torch_dtype=torch.float16,
device_map="auto",
low_cpu_mem_usage=True
)

# 配置 LoRA
peft_config = LoraConfig(
task_type=TaskType.CAUSAL_LM,
inference_mode=False,
r=16,
lora_alpha=32,
lora_dropout=0.05,
target_modules=["q_proj", "k_proj", "v_proj"]
)
model = get_peft_model(model, peft_config)
model.print_trainable_parameters()

# 加载并预处理数据
raw_dataset = load_dataset("json", data_files={"train": "train_data.json"})["train"]
def preprocess_fn(examples):
prompts, labels = [], []
for inst, inp, out in zip(examples["instruction"], examples["input"], examples["output"]):
prompt = f"### 指令:{inst}\n"
if inp and inp.strip():
prompt += f"### 上下文:{inp}\n"
prompt += "### 回答:"
prompts.append(prompt)
labels.append(out)

input_enc = tokenizer(prompts, max_length=512, truncation=True, padding="max_length")
label_enc = tokenizer(labels, max_length=256, truncation=True, padding="max_length")
input_ids, attention_mask = input_enc["input_ids"], input_enc["attention_mask"]
labels_ids = label_enc["input_ids"]
for i in range(len(labels_ids)):
prompt_len = len(tokenizer(prompts[i], return_tensors="pt").input_ids[0])
labels_ids[i][:prompt_len] = [-100] * prompt_len
return {"input_ids": input_ids, "attention_mask": attention_mask, "labels": labels_ids}

tokenized = raw_dataset.map(
preprocess_fn,
batched=True,
remove_columns=raw_dataset.column_names
)
tokenized.set_format(type="torch", columns=["input_ids", "attention_mask", "labels"])

# 训练参数
training_args = TrainingArguments(
output_dir=output_dir,
per_device_train_batch_size=1,
gradient_accumulation_steps=4,
num_train_epochs=3,
learning_rate=3e-4,
fp16=True,
logging_steps=50,
save_steps=200,
save_total_limit=3,
report_to="none"
)

# 定义 Trainer
trainer = Trainer(
model=model,
args=training_args,
train_dataset=tokenized,
data_collator=lambda examples: {
"input_ids": torch.stack([ex["input_ids"] for ex in examples]),
"attention_mask": torch.stack([ex["attention_mask"] for ex in examples]),
"labels": torch.stack([ex["labels"] for ex in examples]),
}
)

# 开始训练
trainer.train()
# 保存微调后模型(包含 LoRA 权重)
model.save_pretrained(output_dir)
tokenizer.save_pretrained(output_dir)

if __name__ == "__main__":
main()

运行方式:

1
2
3
4
5
# 单卡训练示例
CUDA_VISIBLE_DEVICES=0 python finetune_lora_llama7b.py

# 多卡训练示例(如果有 2 张卡)
CUDA_VISIBLE_DEVICES=0,1 python -m torch.distributed.run --nproc_per_node=2 finetune_lora_llama7b.py

5.3 训练超参数与分布式训练配置

  • 学习率(learning_rate)
    • LoRA 常用范围:1e-4 ~ 3e-4
    • 可结合 warmup 步骤(warmup_steps=100)缓冲学习率
  • Batch Size 与梯度累积(gradient_accumulation_steps)
    • 如单卡显存有限,可设 per_device_train_batch_size=1,并使用 gradient_accumulation_steps=4~8
  • Epoch 数
    • 小数据集 1~3 个 epoch 即可
    • 大数据集(数万条以上)可适当增大,但注意过拟合
  • 分布式
    • 若多卡,可使用 torch.distributed.runAccelerate
    • training_args = TrainingArguments(..., deepspeed="ds_config.json") 可启用 DeepSpeed Zero 以进一步节省显存

5.4 检查点保存与模型导出

  • 模型保存

    1
    2
    model.save_pretrained("./lora_llama7b_final/")
    tokenizer.save_pretrained("./lora_llama7b_final/")

    该目录下会包含:

    • pytorch_model.bin(LoRA 权重,与 base_model.bin 共存或可单独保存)
    • adapter_config.json(LoRA 配置)
    • tokenizer.jsontokenizer_config.json
  • 导出合并模型(可选)
    若想将 LoRA 权重与基模型合并为单个 .bin,方便部署,可使用 Hugging Face 提供的 peft 脚本:

    1
    2
    3
    4
    5
    6
    7
    from peft import PeftModel

    # 加载基模型与 LoRA 权重
    base_model = LlamaForCausalLM.from_pretrained("./llama-7b", torch_dtype=torch.float16, device_map="auto")
    lora_model = PeftModel.from_pretrained(base_model, "./lora_llama7b_final/")
    # 导出合并后的模型
    lora_model.save_pretrained("./merged_llama7b/")

    合并后可直接通过 from_pretrained("./merged_llama7b") 加载。

5.5 微调后模型推理对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import torch
from transformers import LlamaForCausalLM, LlamaTokenizer

# 加载微调前后模型
base_model = LlamaForCausalLM.from_pretrained(
"./llama-7b/",
torch_dtype=torch.float16,
device_map="auto"
)
lora_model = LlamaForCausalLM.from_pretrained(
"./lora_llama7b_final/",
torch_dtype=torch.float16,
device_map="auto"
)

tokenizer = LlamaTokenizer.from_pretrained("./llama-7b/")
if tokenizer.pad_token_id is None:
tokenizer.pad_token_id = tokenizer.eos_token_id

prompt = "### 指令:请简单介绍一下人工智能的历史。\n### 回答:"
inputs = tokenizer(prompt, return_tensors="pt").to(base_model.device)

# 微调前
out_base = base_model.generate(
**inputs,
max_new_tokens=100,
temperature=0.7,
top_p=0.9,
do_sample=True,
)
print("=== 微调前 输出 ===")
print(tokenizer.decode(out_base[0], skip_special_tokens=True))

# 微调后
inputs_lora = tokenizer(prompt, return_tensors="pt").to(lora_model.device)
out_lora = lora_model.generate(
**inputs_lora,
max_new_tokens=100,
temperature=0.7,
top_p=0.9,
do_sample=True,
)
print("\n=== 微调后 输出 ===")
print(tokenizer.decode(out_lora[0], skip_special_tokens=True))

对比两者在针对相同“指令”下的回答差异,验证 LoRA 微调效果。


6. 推理部署优化

在本地部署微调或预训练模型时,除了直接使用 CPU/GPU 生成外,还可考虑更高级的加速方式。

6.1 8-bit / 4-bit 量化推理

如 3.3 小节所述,使用 bitsandbytes 将模型参数量化,可节省显存并提高推理速度;对已微调模型同样适用:

1
2
3
4
5
6
7
8
9
10
11
12
13
from transformers import AutoModelForCausalLM, AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("./lora_llama7b_final/")
model = AutoModelForCausalLM.from_pretrained(
"./lora_llama7b_final/",
load_in_8bit=True,
device_map="auto"
)

# 推理示例
inputs = tokenizer("今天的新闻有哪些热点?", return_tensors="pt").to(model.device)
out = model.generate(**inputs, max_new_tokens=50)
print(tokenizer.decode(out[0], skip_special_tokens=True))

6.2 ONNX 转换与加速

将模型导出为 ONNX 后,可使用 ONNX Runtime、TensorRT 等进行推理优化。基本流程:

  1. 导出 ONNX

    1
    pip install onnx onnxruntime onnxruntime-gpu
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    from transformers import AutoModelForCausalLM, AutoTokenizer
    import torch

    model = AutoModelForCausalLM.from_pretrained("./llama-7b/", torch_dtype=torch.float16).half().cuda()
    tokenizer = AutoTokenizer.from_pretrained("./llama-7b/")

    # 示例导出:仅导出编码部分为 ONNX
    input_str = "测试"
    inputs = tokenizer(input_str, return_tensors="pt").to(model.device)
    torch.onnx.export(
    model,
    (inputs["input_ids"], inputs["attention_mask"]),
    "llama7b.onnx",
    input_names=["input_ids", "attention_mask"],
    output_names=["output"],
    dynamic_axes={
    "input_ids": {0: "batch", 1: "sequence"},
    "attention_mask": {0: "batch", 1: "sequence"},
    "output": {0: "batch", 1: "sequence"}
    },
    opset_version=13,
    do_constant_folding=True,
    use_external_data_format=False
    )
  2. 使用 ONNX Runtime 推理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    import onnxruntime as ort
    from transformers import AutoTokenizer
    import numpy as np

    session = ort.InferenceSession("llama7b.onnx", providers=["CUDAExecutionProvider"])
    tokenizer = AutoTokenizer.from_pretrained("./llama-7b/")

    input_str = "请概述物理学的发展史。"
    inputs = tokenizer(input_str, return_tensors="np")
    onnx_inputs = {
    "input_ids": inputs["input_ids"].astype(np.int64),
    "attention_mask": inputs["attention_mask"].astype(np.int64)
    }
    outputs = session.run(None, onnx_inputs)
    # outputs[0] 为 logits,可自行后处理生成文本

提示:ONNX 转换与后续推理需要额外编写生成逻辑(如 Greedy / Beam Search),社区工具如 onnxruntime-transformers 可简化这一流程。

6.3 TensorRT / TVM 简述

  • TensorRT:NVIDIA 推出的高性能推理库,支持 FP16、INT8、动态 shape 等。可通过 torch2trtonnx-tensorrt 等工具将 PyTorch/ONNX 模型导入 TensorRT。
  • Apache TVM:开源深度学习编译器,可针对不同硬件自动生成高效执行代码。

由于配置与使用较为复杂,这里仅简要提及。若有需求,可参考官方文档进行实验。

6.4 多 GPU / 多机部署示例

  • Accelerate:Hugging Face 官方推荐工具,可通过交互式配置自动完成分布式训练与推理。例如:

    1
    accelerate config

    根据提示选择单机多卡 / 多机多卡,填写主机 IP、端口、进程数等信息。完成后,可直接:

    1
    accelerate launch finetune_lora_llama7b.py
  • DeepSpeed:编写 ds_config.json 配置文件,启用 ZeRO 分布式优化。例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    {
    "train_micro_batch_size_per_gpu": 1,
    "gradient_accumulation_steps": 4,
    "optimizer": {
    "type": "AdamW",
    "params": {
    "lr": 3e-4,
    "betas": [0.9, 0.999],
    "eps": 1e-8
    }
    },
    "zero_optimization": {
    "stage": 2
    },
    "fp16": {"enabled": true}
    }

    然后:

    1
    deepspeed --num_gpus=2 finetune_lora_llama7b.py --deepspeed ds_config.json

7. 常见问题与调优建议

7.1 显存不足解决思路

  1. 使用 8-bit/4-bit 量化:显存节省 2~4 倍,适合推理与微调。
  2. 启用梯度累积(gradient_accumulation_steps):减小批量大小,使用多步梯度累计代替大批量。
  3. 开启梯度检查点model.gradient_checkpointing_enable()):在前向时丢弃部分中间激活,反向时重新计算,节省显存,但计算速度稍慢。
  4. 冻结部分层:若全参数微调,可冻结底层若干 Transformer 层,仅针对上层进行训练。
  5. 使用 CPU+GPU 混合训练:将部分参数(如 Embedding 层)放到 CPU,减少 GPU 显存消耗。可使用 DeepSpeed CPU Offload 功能。

7.2 学习率、Batch Size 调试

  • 学习率
    • LoRA 微调常用 1e-4 ~ 3e-4;若数据量较小,可适当降低至 5e-5。
    • 全参数微调一般使用 1e-5 ~ 1e-4,不同模型需调试。
  • Batch Size
    • 若显存允许,可尝试 per_device_train_batch_size=2~4;否则用 1,配合梯度累积。
    • 关注训练损失曲线,若抖动剧烈,可尝试增大 batch 或添加学习率暖启动(warmup)。

7.3 收敛慢或过拟合应对

  • 收敛慢
    • 增大学习率或调整学习率调度器(Scheduler)。
    • 增加训练数据量或数据多样性;
    • 检查数据预处理是否存在错误(如填充过长导致大部分为 -100)。
  • 过拟合
    • 增加正则化(如 LoRA dropout);
    • 减少训练 epoch,或提前停止(Early Stopping);
    • 数据增强、数据集划分合理(训练/验证/测试)。

7.4 推理速度瓶颈分析

  • 显存带宽与计算能力:若 GPU 计算能力不足,推理耗时较高,可降 precision(FP16、INT8)。
  • 生成策略:Beam Search 会比 Greedy 或 Top-p 采样更慢,如无必要可使用 sampling。
  • I/O 与 CPU 预处理:避免每次 generate 都重新加载 tokenizer 等,可将模型持久加载在进程中。
  • 并发请求:可使用 FastAPI、Flask 部署时结合 NVIDIA Triton Inference Server 或 NVIDIA TensorRT Inference Server 提升吞吐。

8. 附录

8.1 常用平台与工具链接

8.2 参考文献与官方文档

  1. Hu, E., Shen, Y., Wallis, P., Allen-Zhu, Z., Li, Y., Wang, L., Wang, L., & Chen, W. (2021). LoRA: Low-Rank Adaptation of Large Language Models. arXiv preprint arXiv:2106.09685.
  2. Lester, B., Al-Rfou, R., & Constant, N. (2021). The Power of Scale for Parameter-Efficient Prompt Tuning. EMNLP 2021.
  3. Wolf, T., Debut, L., Sanh, V., Chaumond, J., Delangue, C., Moi, A., Cistac, P., Rault, T., Louf, R., Funtowicz, M., & Brew, J. (2020). Transformers: State-of-the-Art Natural Language Processing. ACL 2020.
  4. Sharma, A., Shi, Y., & Peters, M. (2023). Parameter-Efficient Fine-Tuning for Pretrained Language Models: A Survey. arXiv preprint arXiv:2304.03733.
  5. NVIDIA. (2023). TensorRT Documentation.
  6. Microsoft. (2023). DeepSpeed Documentation.

通过以上手册内容,读者应能掌握在本地环境下从零配置、下载预训练模型、进行推理、到高效微调并部署的全流程。后续可根据实际需求,深入研究更多加速技术(如 ZeRO-Offload、Triton Inference Server 等),以在有限硬件资源下最大化地发挥大语言模型的能力。祝使用顺利,如有疑问,可参考上述官方文档或社区示例,不断迭代优化。

Contents
  1. 1. 本地部署大模型以及微调手册
    1. 1.1. 概述
    2. 1.2. 目录
    3. 1.3. 1. 前置条件
      1. 1.3.1. 1.1 硬件要求
      2. 1.3.2. 1.2 软件与环境依赖
      3. 1.3.3. 1.3 概念术语说明
    4. 1.4. 2. 环境准备
      1. 1.4.1. 2.1 操作系统与基础依赖安装
      2. 1.4.2. 2.2 Python 环境与包管理
      3. 1.4.3. 2.3 CUDA、cuDNN、NCCL 安装与验证
      4. 1.4.4. 2.4 Conda/venv 虚拟环境配置示例
    5. 1.5. 3. 模型获取与部署
      1. 1.5.1. 3.1 Hugging Face Transformers 概览
      2. 1.5.2. 3.2 下载与加载预训练模型
      3. 1.5.3. 3.3 模型量化与显存优化
      4. 1.5.4. 3.4 推理示例代码
      5. 1.5.5. 3.5 使用 DeepSpeed / In-8bit 推理加速
    6. 1.6. 4. 微调策略与实现
      1. 1.6.1. 4.1 微调方法概述
        1. 1.6.1.1. 4.1.1 全参数微调(Full Fine-tuning)
        2. 1.6.1.2. 4.1.2 LoRA(Low-Rank Adaptation)
        3. 1.6.1.3. 4.1.3 P-tuning / Prompt Tuning
      2. 1.6.2. 4.2 全参数微调(Full Fine-tuning)详解
      3. 1.6.3. 4.3 LoRA 实现要点
      4. 1.6.4. 4.4 P-tuning / Prompt Tuning 详解
      5. 1.6.5. 4.5 PEFT(Parameter-Efficient Fine-Tuning)架构介绍
      6. 1.6.6. 4.6 数据集准备与预处理
    7. 1.7. 5. 实战示例:使用 LoRA 微调 LLaMA-7B
      1. 1.7.1. 5.1 数据集选取与格式化
      2. 1.7.2. 5.2 PEFT + Transformers 微调脚本示例
      3. 1.7.3. 5.3 训练超参数与分布式训练配置
      4. 1.7.4. 5.4 检查点保存与模型导出
      5. 1.7.5. 5.5 微调后模型推理对比
    8. 1.8. 6. 推理部署优化
      1. 1.8.1. 6.1 8-bit / 4-bit 量化推理
      2. 1.8.2. 6.2 ONNX 转换与加速
      3. 1.8.3. 6.3 TensorRT / TVM 简述
      4. 1.8.4. 6.4 多 GPU / 多机部署示例
    9. 1.9. 7. 常见问题与调优建议
      1. 1.9.1. 7.1 显存不足解决思路
      2. 1.9.2. 7.2 学习率、Batch Size 调试
      3. 1.9.3. 7.3 收敛慢或过拟合应对
      4. 1.9.4. 7.4 推理速度瓶颈分析
    10. 1.10. 8. 附录
      1. 1.10.1. 8.1 常用平台与工具链接
      2. 1.10.2. 8.2 参考文献与官方文档
|