# 微调 LLM

使用高效微调技术在 CLORE.AI GPU 上训练您自己的自定义大语言模型。

{% hint style="success" %}
所有示例均可在通过以下方式租用的 GPU 服务器上运行 [CLORE.AI 市场](https://clore.ai/marketplace).
{% endhint %}

## 在 CLORE.AI 上租用

1. 访问 [CLORE.AI 市场](https://clore.ai/marketplace)
2. 按 GPU 类型、显存和价格筛选
3. 选择 **按需（On-Demand）** （固定费率）或 **竞价（Spot）** （出价价格）
4. 配置您的订单：
   * 选择 Docker 镜像
   * 设置端口（SSH 使用 TCP，Web 界面使用 HTTP）
   * 如需可添加环境变量
   * 输入启动命令
5. 选择付款方式： **CLORE**, **BTC**，或 **USDT/USDC**
6. 创建订单并等待部署

### 访问您的服务器

* 在以下位置查找连接详情 **我的订单**
* Web 界面：使用 HTTP 端口的 URL
* SSH： `ssh -p <port> root@<proxy-address>`

## 什么是 LoRA/QLoRA？

* **LoRA** （低秩适配）— 训练小型适配器层而不是整个模型
* **QLoRA** — 带量化的 LoRA，可进一步减少显存占用
* 在单卡 RTX 3090 上训练 7B 模型
* 在单卡 A100 上训练 70B 模型

## 要求

| 模型  | 方法      | 最低显存 | 推荐        |
| --- | ------- | ---- | --------- |
| 7B  | QLoRA   | 12GB | RTX 3090  |
| 13B | QLoRA   | 20GB | RTX 4090  |
| 70B | QLoRA   | 48GB | A100 80GB |
| 7B  | 完整 LoRA | 24GB | RTX 4090  |

## 快速部署

**Docker 镜像：**

```
pytorch/pytorch:2.5.1-cuda12.4-cudnn9-devel
```

**端口：**

```
22/tcp
8888/http
6006/http
```

**命令：**

```bash
pip install "transformers>=4.45" "datasets>=2.20" accelerate "peft>=0.14" \
    bitsandbytes "trl>=0.12" wandb jupyterlab && \
jupyter lab --ip=0.0.0.0 --port=8888 --allow-root
```

## 访问您的服务

部署后，在以下位置查找您的 `http_pub` URL **我的订单**:

1. 转到 **我的订单** 页面
2. 点击您的订单
3. 查找 `http_pub` URL（例如， `abc123.clorecloud.net`)

使用 `https://YOUR_HTTP_PUB_URL` 替代 `localhost` 在下面的示例中。

## 数据集准备

### 聊天格式（推荐）

```json
[
  {
    "messages": [
      {"role": "system", "content": "You are a helpful assistant."},
      {"role": "user", "content": "What is Python?"},
      {"role": "assistant", "content": "Python is a programming language..."}
    ]
  }
]
```

### 指令格式

```json
[
  {
    "instruction": "Translate to French",
    "input": "Hello, how are you?",
    "output": "Bonjour, comment allez-vous?"
  }
]
```

### Alpaca 格式

```json
[
  {
    "instruction": "Give three tips for staying healthy.",
    "input": "",
    "output": "1. Eat balanced meals..."
  }
]
```

## 支持的现代模型（2025）

| 模型                         | HF ID                                     | 最低显存（QLoRA） |
| -------------------------- | ----------------------------------------- | ----------- |
| Llama 3.1 / 3.3 8B         | `meta-llama/Llama-3.1-8B-Instruct`        | 12GB        |
| Qwen 2.5 7B / 14B          | `Qwen/Qwen2.5-7B-Instruct`                | 12GB / 20GB |
| DeepSeek-R1-Distill（7B/8B） | `deepseek-ai/DeepSeek-R1-Distill-Qwen-7B` | 12GB        |
| Mistral 7B v0.3            | `mistralai/Mistral-7B-Instruct-v0.3`      | 12GB        |
| Gemma 2 9B                 | `google/gemma-2-9b-it`                    | 14GB        |
| Phi-4 14B                  | `microsoft/phi-4`                         | 20GB        |

## QLoRA 微调脚本

现代示例，支持 PEFT 0.14+、Flash Attention 2、DoRA，以及 Qwen2.5 / DeepSeek-R1 兼容性：

```python
import torch
from transformers import (
    AutoModelForCausalLM,
    AutoTokenizer,
    BitsAndBytesConfig,
    TrainingArguments,
)
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
from datasets import load_dataset
from trl import SFTTrainer, SFTConfig

# === 配置 ===
# 选择其一：Qwen2.5、DeepSeek-R1-Distill、Llama 3.1、Mistral 等。
MODEL_NAME = "Qwen/Qwen2.5-7B-Instruct"
# MODEL_NAME = "deepseek-ai/DeepSeek-R1-Distill-Qwen-7B"
# MODEL_NAME = "meta-llama/Llama-3.1-8B-Instruct"

DATASET = "your_dataset.json"  # 或 HuggingFace 数据集名
OUTPUT_DIR = "./output"
MAX_SEQ_LENGTH = 4096           # Qwen2.5 支持最高 32K 上下文
USE_DORA = True                 # DoRA 相对于标准 LoRA 提升质量
USE_FLASH_ATTN = True           # Flash Attention 2 节省显存并加速

# === 使用 4-bit 量化加载模型 ===
bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=torch.bfloat16,
    bnb_4bit_use_double_quant=True,
)

model = AutoModelForCausalLM.from_pretrained(
    MODEL_NAME,
    quantization_config=bnb_config,
    device_map="auto",
    trust_remote_code=True,  # Qwen2.5 和 DeepSeek 必需
    # Flash Attention 2：需要安培架构及以上 GPU（RTX 30/40，A100）
    attn_implementation="flash_attention_2" if USE_FLASH_ATTN else "eager",
)

tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME, trust_remote_code=True)
if tokenizer.pad_token is None:
    tokenizer.pad_token = tokenizer.eos_token
tokenizer.padding_side = "right"

# === 配置 LoRA（可选 DoRA） ===
# DoRA（权重分解的低秩适配）— 需要 PEFT >= 0.14
# use_dora=True 会将权重分解为幅度 + 方向，以获得更好质量
lora_config = LoraConfig(
    r=64,                    # 秩（越大=容量越高，显存越多）
    lora_alpha=16,           # 缩放因子（保持等于或为 r 的一半）
    target_modules=[
        "q_proj", "k_proj", "v_proj", "o_proj",  # 注意力层
        "gate_proj", "up_proj", "down_proj",      # MLP 层
    ],
    lora_dropout=0.05,
    bias="none",
    task_type="CAUSAL_LM",
    use_dora=USE_DORA,        # DoRA：提升质量（PEFT 0.14+）
    # use_rslora=True,        # 可选：秩稳定 LoRA
)

# 为 QLoRA 训练准备模型
model = prepare_model_for_kbit_training(
    model,
    use_gradient_checkpointing=True,
    gradient_checkpointing_kwargs={"use_reentrant": False},
)
model = get_peft_model(model, lora_config)

# 打印可训练参数摘要
model.print_trainable_parameters()
# 示例输出：trainable params: 42,991,616 || all params: 7,284,891,648 || trainable%: 0.59

# === 加载数据集 ===
dataset = load_dataset("json", data_files=DATASET)
# 或使用公开数据集：
# dataset = load_dataset("HuggingFaceH4/ultrachat_200k")

# === 将数据集格式化为 Qwen2.5 / ChatML 格式 ===
def format_chat_qwen(example):
    """使用 ChatML 模板为 Qwen2.5 格式化。"""
    messages = example.get("messages", [])
    if not messages:
        # 处理 alpaca 风格数据
        text = f"<|im_start|>system\nYou are a helpful assistant.<|im_end|>\n"
        text += f"<|im_start|>user\n{example['instruction']}"
        if example.get("input"):
            text += f"\n{example['input']}"
        text += f"<|im_end|>\n<|im_start|>assistant\n{example['output']}<|im_end|>"
    else:
        # 处理 messages 格式（ChatML）
        text = tokenizer.apply_chat_template(
            messages,
            tokenize=False,
            add_generation_prompt=False,
        )
    return {"text": text}

dataset = dataset.map(format_chat_qwen, remove_columns=dataset["train"].column_names)

# === 训练参数（PEFT 0.14+ / TRL 0.12+） ===
training_args = SFTConfig(
    output_dir=OUTPUT_DIR,
    num_train_epochs=3,
    per_device_train_batch_size=2,
    gradient_accumulation_steps=8,         # 实际批次 = 2 * 8 = 16
    learning_rate=2e-4,
    weight_decay=0.001,
    warmup_ratio=0.03,
    lr_scheduler_type="cosine",
    logging_steps=10,
    save_steps=100,
    save_total_limit=3,
    bf16=True,                             # 在现代 GPU（A100、RTX 30/40）上使用 bf16
    # fp16=True,                           # 在较旧 GPU 上使用 fp16
    optim="paged_adamw_8bit",
    max_grad_norm=0.3,
    group_by_length=True,
    report_to="wandb",                     # 或 "tensorboard"
    # SFTConfig 特有：
    max_seq_length=MAX_SEQ_LENGTH,
    dataset_text_field="text",
    packing=True,                          # 为提高效率打包多个示例
)

# === 开始训练 ===
trainer = SFTTrainer(
    model=model,
    train_dataset=dataset["train"],
    tokenizer=tokenizer,
    args=training_args,
)

trainer.train()

# === 保存 LoRA 适配器 ===
trainer.save_model(f"{OUTPUT_DIR}/final")
tokenizer.save_pretrained(f"{OUTPUT_DIR}/final")
print(f"Model saved to {OUTPUT_DIR}/final")
```

## Flash Attention 2

Flash Attention 2 显著降低显存使用并加速训练。需要安培及以上 GPU（RTX 3090、RTX 4090、A100）。

```bash
# 安装 Flash Attention 2
pip install flash-attn --no-build-isolation
```

```python
# 在模型加载中启用：
model = AutoModelForCausalLM.from_pretrained(
    MODEL_NAME,
    attn_implementation="flash_attention_2",  # <-- 添加此项
    torch_dtype=torch.bfloat16,               # FA2 需要 bf16 或 fp16
    device_map="auto",
)
```

| 设置                        | 显存（7B） | 速度   |
| ------------------------- | ------ | ---- |
| 标准注意力（fp16）               | \~22GB | 基线   |
| Flash Attention 2（bf16）   | \~16GB | +30% |
| Flash Attention 2 + QLoRA | \~12GB | +30% |

## DoRA（权重分解的 LoRA）

DoRA（PEFT >= 0.14）将预训练权重分解为幅度和方向分量。它提高了微调质量，尤其是在较小秩时表现突出。

```python
from peft import LoraConfig

# 标准 LoRA
lora_config = LoraConfig(r=64, lora_alpha=16, use_dora=False, ...)

# DoRA — 相同参数，更好质量
lora_config = LoraConfig(r=64, lora_alpha=16, use_dora=True, ...)
# 注意：与标准 LoRA 相比，DoRA 会增加约 5-10% 的显存开销
# 注意：在某些情况下与量化（4-bit/8-bit）模型不兼容
```

## Qwen2.5 与 DeepSeek-R1-Distill 示例

### Qwen2.5 微调

```python
MODEL_NAME = "Qwen/Qwen2.5-7B-Instruct"
# 对于 14B："Qwen/Qwen2.5-14B-Instruct"（使用 QLoRA 需要 20GB+ 显存）

model = AutoModelForCausalLM.from_pretrained(
    MODEL_NAME,
    quantization_config=bnb_config,
    device_map="auto",
    trust_remote_code=True,          # Qwen2.5 必需
    attn_implementation="flash_attention_2",
)
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME, trust_remote_code=True)

# Qwen2.5 使用 ChatML 格式 — 使用 apply_chat_template
messages = [
    {"role": "system", "content": "You are a helpful assistant."},
    {"role": "user", "content": "Hello!"},
    {"role": "assistant", "content": "Hi there! How can I help?"},
]
text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=False)
```

### DeepSeek-R1-Distill 微调

DeepSeek-R1-Distill 模型（Qwen-7B、Qwen-14B、Llama-8B、Llama-70B）侧重推理。微调以将其链式思维（chain-of-thought）风格适配到您的领域。

```python
# DeepSeek-R1-Dist分支
MODEL_NAME = "deepseek-ai/DeepSeek-R1-Distill-Qwen-7B"   # 基于 Qwen2.5 的 7B
# MODEL_NAME = "deepseek-ai/DeepSeek-R1-Distill-Llama-8B" # 基于 Llama3 的 8B
# MODEL_NAME = "deepseek-ai/DeepSeek-R1-Distill-Qwen-14B" # 14B（需要 A100）

model = AutoModelForCausalLM.from_pretrained(
    MODEL_NAME,
    quantization_config=bnb_config,
    device_map="auto",
    trust_remote_code=True,
    attn_implementation="flash_attention_2",
)

# DeepSeek-R1 在推理中使用 <think>...</think> 标签
# 在训练数据中保留这些标签以保留链式思维能力
example_format = """<|im_start|>user
Solve: What is 15 * 23?<|im_end|>",
<|im_start|>assistant
<think>
15 * 23 = 15 * 20 + 15 * 3 = 300 + 45 = 345
</think>
The answer is 345.<|im_end|>"""

# DeepSeek-R1-Distill 的 LoRA 目标模块（基于 Qwen2.5）
lora_config = LoraConfig(
    r=32,
    lora_alpha=16,
    target_modules=["q_proj", "k_proj", "v_proj", "o_proj",
                    "gate_proj", "up_proj", "down_proj"],
    use_dora=True,
    task_type="CAUSAL_LM",
)
```

## 使用 Axolotl（更简单）

Axolotl 使用 YAML 配置简化微调：

```bash
pip install axolotl

# 创建配置
cat > config.yml << 'EOF'
base_model: mistralai/Mistral-7B-v0.1
model_type: MistralForCausalLM
tokenizer_type: LlamaTokenizer

load_in_4bit: true
adapter: qlora
lora_r: 32
lora_alpha: 16

datasets:
  - path: your_data.json
      type: alpaca

sequence_len: 4096
sample_packing: true

gradient_accumulation_steps: 4
micro_batch_size: 2
num_epochs: 3
learning_rate: 2e-4

output_dir: ./output
EOF

# 训练
accelerate launch -m axolotl.cli.train config.yml
```

## Axolotl 配置示例

### 聊天模型

```yaml
base_model: mistralai/Mistral-7B-Instruct-v0.2
load_in_4bit: true
adapter: qlora

datasets:
  - path: data.json
      type: sharegpt

chat_template: mistral
```

### 代码模型

```yaml
base_model: codellama/CodeLlama-7b-hf
load_in_4bit: true
adapter: qlora

datasets:
  - path: code_data.json
      type: alpaca

sequence_len: 8192  # 代码需要更长的上下文
```

## 合并 LoRA 权重

训练后，将 LoRA 合并回基础模型：

```python
from peft import PeftModel
from transformers import AutoModelForCausalLM, AutoTokenizer

# 加载基础模型
base_model = AutoModelForCausalLM.from_pretrained(
    "mistralai/Mistral-7B-v0.1",
    torch_dtype=torch.float16,
    device_map="auto",
)

# 加载 LoRA
model = PeftModel.from_pretrained(base_model, "./output/final")

# 合并
merged_model = model.merge_and_unload()

# 保存合并后的模型
merged_model.save_pretrained("./merged_model")
tokenizer.save_pretrained("./merged_model")
```

## 转换为 GGUF

用于 llama.cpp/Ollama：

```bash

# 克隆 llama.cpp
git clone https://github.com/ggerganov/llama.cpp
cd llama.cpp

# 转换
python convert.py ../merged_model --outtype f16 --outfile model-f16.gguf

# 量化
./quantize model-f16.gguf model-q4_k_m.gguf q4_k_m
```

## 监控训练

### Weights & Biases

```python
import wandb
wandb.init(project="llm-finetune", name="mistral-7b-lora")
```

### TensorBoard

```python

# 在训练参数中
report_to="tensorboard"
logging_dir="./logs"

# 查看
tensorboard --logdir ./logs --port 6006 --bind_all
```

## 最佳实践

### 超参数

| 参数           | 7B 模型 | 13B 模型 | 70B 模型 |
| ------------ | ----- | ------ | ------ |
| batch\_size  | 4     | 2      | 1      |
| grad\_accum  | 4     | 8      | 16     |
| 学习率（lr）      | 2e-4  | 1e-4   | 5e-5   |
| lora\_r      | 64    | 32     | 16     |
| 训练轮数（epochs） | 3     | 2-3    | 1-2    |

### 数据集规模

* 最少：1,000 个示例
* 良好：10,000+ 个示例
* 质量优于数量

### 避免过拟合

```python
training_args = TrainingArguments(
    ...
    weight_decay=0.01,
    warmup_ratio=0.03,
    save_total_limit=3,
    load_best_model_at_end=True,
    evaluation_strategy="steps",
    eval_steps=100,
)
```

## 多卡训练

```bash

# 使用 accelerate
accelerate launch --multi_gpu --num_processes 4 train.py

# 使用 DeepSpeed
accelerate launch --use_deepspeed --num_processes 4 train.py
```

DeepSpeed 配置：

```json
{
  "bf16": {"enabled": true},
  "zero_optimization": {
      "stage": 2,
      "offload_optimizer": {"device": "cpu"}
  }
}
```

## 保存与导出

```bash

# 保存 LoRA 适配器
trainer.save_model("./lora_adapter")

# 保存合并后的模型
merged_model.save_pretrained("./full_model")

# 上传到 HuggingFace
huggingface-cli login
merged_model.push_to_hub("username/my-model")
```

## 故障排查

### OOM 错误

* 减少批次大小
* 增加梯度累积
* 使用 `gradient_checkpointing=True`
* 减少 lora\_r

### 训练损失未下降

* 检查数据格式
* 提高学习率
* 检查数据问题

### 损失为 NaN

* 降低学习率
* 使用 fp32 而不是 fp16
* 检查是否有损坏的数据

## 成本估算

典型 CLORE.AI 市场价格（截至 2024 年）：

| GPU       | 小时费率    | 日费率     | 4 小时会话  |
| --------- | ------- | ------- | ------- |
| RTX 3060  | \~$0.03 | \~$0.70 | \~$0.12 |
| RTX 3090  | \~$0.06 | \~$1.50 | \~$0.25 |
| RTX 4090  | \~$0.10 | \~$2.30 | \~$0.40 |
| A100 40GB | \~$0.17 | \~$4.00 | \~$0.70 |
| A100 80GB | \~$0.25 | \~$6.00 | \~$1.00 |

*价格因提供商和需求而异。请查看* [*CLORE.AI 市场*](https://clore.ai/marketplace) *以获取当前费率。*

> 📚 另见： [在云 GPU 上微调 LLaMA 3 的逐步指南](https://blog.clore.ai/how-to-fine-tune-llama-3-cloud-gpu/)

**省钱：**

* 使用 **竞价（Spot）** 适合弹性工作负载的市场（通常便宜 30-50%）
* 支付方式 **CLORE** 代币
* 比较不同提供商的价格
