在博客笔记上微调Qwen3-4B | AI生成和翻译

Home 2026.06

以下是整个过程的完整端到端故事:

══════════════════════════════════════════════════════════════
  如何在个人博客笔记上微调 QWEN3-4B
  完整流水线:数据 → 训练 → 合并 → GGUF
══════════════════════════════════════════════════════════════

┌─────────────────────────────────────────────────────────────┐
│  步骤 1: 数据 — build_dataset.py                            │
│  来源: ~/projects/jekyll-ai-blog/_posts/{en,zh}/*.md       │
│                                                             │
│  我们抓取了你个人 Jekyll 博客的英语和中文 Markdown 文章,    │
│  包含 frontmatter(标题、正文、元数据)。                    │
│                                                             │
│  清洗流程:                                                  │
│    - 去除 Jekyll/Liquid 标签 ({% %}, {{ }})                 │
│    - 去除 kramdown 属性列表 ({: .centered })                 │
│    - 去除图片引用 (![](assets/...))                          │
│    - 压缩多余空白                                            │
│    - 删除长度小于100字符的文章                                │
│                                                             │
│  格式: 每个样本转换为一个 "conversations" 数组:              │
│    {                                                        │
│      "conversations": [                                     │
│        {"role": "user", "content": "<博客标题>"},            │
│        {"role": "assistant", "content": "<清洗后的正文>"}    │
│      ]                                                      │
│    }                                                        │
│                                                             │
│  任务: 给定一个博客标题,生成完整的博客正文。                │
│  模型学会仅通过标题提示来重构你的写作风格、知识和内容。      │
│                                                             │
│  划分: 21,234 训练 / 200 评估 / 21,434 总计                 │
│  大小: 约86MB原始文本,约32.5M tokens 已处理                 │
└─────────────────────────────────────────────────────────────┘

                              │
                              ▼

┌─────────────────────────────────────────────────────────────┐
│  步骤 2: 训练 — train.py                                    │
│  框架: 纯 transformers + peft + trl (SFTTrainer)             │
│                                                             │
│  基础模型:                                                  │
│    unsloth/Qwen3-4B-unsloth-bnb-4bit                        │
│    - Qwen3 4B参数,预量化为4-bit (BNB)                       │
│    - 适配12GB显存(RTX 4070),并留有训练空间                 │
│    - 通过 HuggingFace 磁盘缓存加载                           │
│                                                             │
│  LoRA 配置:                                                 │
│    r = 32  (秩 — 低秩分解维度)                              │
│    alpha = 32  (缩放因子,alpha/r = 1.0)                    │
│    dropout = 0                                              │
│    目标模块:                                                │
│      q_proj, k_proj, v_proj, o_proj  (注意力机制)           │
│      gate_proj, up_proj, down_proj   (MLP/FFN)              │
│    → 每个 Transformer 层的全部7个权重矩阵                    │
│    → 仅约1-2%的参数是可训练的(LoRA A/B)                    │
│                                                             │
│  为什么用 LoRA? 不是微调全部4B参数,而是                     │
│  在每个权重矩阵旁注入小型秩为32的矩阵。                      │
│  原始权重被冻结,只训练 LoRA 的差值部分。                    │
│  结果: 约80MB适配器 vs 约8GB完整模型。                       │
│                                                             │
│  训练超参数:                                                │
│    batch_size = 2  ×  grad_accum = 8  = 有效批次 16         │
│    epochs = 2                                               │
│    lr = 2e-4  (余弦调度,预热3%)                            │
│    bf16 = True                                              │
│    max_seq_len = 4096                                       │
│    seed = 42                                                │
│                                                             │
│  运行时间: 约10小时52分钟(RTX 4070)                        │
│  步数: 共2,656步                                            │
│                                                             │
│  最终指标:                                                  │
│    loss: 1.417                                              │
│    平均 token 准确率: 65.6%                                  │
│    梯度范数: 约0.033(全程稳定)                              │
│    学习率: 完全衰减至约3.6e-09                               │
│                                                             │
│  输出: /mnt/data/zz/finetune/lzw-notes-lora/                │
│    adapter_config.json                                      │
│    adapter_model.safetensors  (约80MB)                     │
│    tokenizer.json + tokenizer_config.json                   │
│    chat_template.jinja                                      │
└─────────────────────────────────────────────────────────────┘

                              │
                              ▼

┌─────────────────────────────────────────────────────────────┐
│  步骤 3: 合并 — merge.py                                    │
│                                                             │
│  问题: LoRA 适配器是一个增量(DELTA)——它只在推理时          │
│  与基础模型结合才能工作。                                    │
│  为了独立部署,需要将 LoRA 权重合并回基础模型。              │
│                                                             │
│  流程:                                                      │
│    1. 加载 FP16 基础模型: unsloth/Qwen3-4B                  │
│       (注意:不是训练时的4-bit副本——需要全精度)             │
│    2. 从 lzw-notes-lora/ 加载 LoRA 适配器                    │
│    3. 调用 model.merge_and_unload()                         │
│       → 计算: W_merged = W_base + alpha/r * (A @ B)         │
│       → 对7个目标模块在每一层执行                            │
│    4. 保存合并后的模型(约8GB FP16)                         │
│                                                             │
│  输出: /mnt/data/zz/finetune/lzw-notes-merged/              │
│    融合了你的博客知识的完整 Qwen3-4B 模型                    │
└─────────────────────────────────────────────────────────────┘

                              │
                              ▼

┌─────────────────────────────────────────────────────────────┐
│  步骤 4: GGUF 导出 — export_gguf.py                         │
│                                                             │
│  GGUF = llama.cpp / ollama 的标准格式。                      │
│  我们将 FP16(8GB)量化到 Q4_K_M(2.4GB)。                  │
│                                                             │
│  流程(通过 unsloth + llama.cpp):                           │
│    1. 加载合并后的 FP16 模型                                │
│    2. 转换 HF → GGUF bf16  (约3分钟)                      │
│    3. 量化 bf16 → Q4_K_M  (约10分钟)                      │
│       → 4-bit,混合精度(K-quant,中等质量)                  │
│       → 模型大小缩减约3.3倍,质量损失极小                     │
│                                                             │
│  输出: /mnt/data/zz/finetune/lzw-notes-merged_gguf/         │
│    lzw-notes-merged.Q4_K_M.gguf  (2.4 GB)                 │
│                                                             │
│  可用于:                                                    │
│    - ollama create lzw-notes -f Modelfile                   │
│    - llama-cli --model ...Q4_K_M.gguf -p "prompt"           │
│    - 任何兼容 GGUF 的运行时                                  │
└─────────────────────────────────────────────────────────────┘


══════════════════════════════════════════════════════════════
  流水线总结
══════════════════════════════════════════════════════════════

  博客文章 (.md)
       │
       ▼  build_dataset.py  (清洗 + 格式化为对话)
  SFT 数据集(21K 样本,约32.5M tokens)
       │
       ▼  train.py  (4-bit Qwen3-4B 上的 LoRA,2轮,约11小时)
  LoRA 适配器(约80MB)
       │
       ▼  merge.py  (将 LoRA 合并到 FP16 基础模型)
  合并后的模型(约8GB FP16)
       │
       ▼  export_gguf.py  (量化到 Q4_K_M)
  GGUF 文件(2.4GB)→ 通过 ollama / llama.cpp 部署

══════════════════════════════════════════════════════════════
  关键设计决策
══════════════════════════════════════════════════════════════

  1. 为什么选 QWEN3-4B?
     在12GB显存下最佳质量/大小比。4B参数提供足够容量
     来学习写作风格和内容,无需企业级硬件。

  2. 为什么用4-bit基础 + LoRA(而非全量微调)?
     基础模型的4-bit量化(QLoRA风格)将显存需求从约16GB
     降至约4GB,为单张RTX 4070上的梯度和优化器状态留出空间。
     然后LoRA仅训练约1-2%的参数,保持内存可控。

  3. 为什么用标题→正文格式(而非聊天或指令)?
     从博客数据中衍生出的最简单的SFT格式。模型学习:
     "给定一个标题,以该作者的风格和知识生成完整文章。"
     适用于内容生成、RAG增强或个人写作助手场景。

  4. 为什么用 Q4_K_M 量化?
     部署时最佳质量/大小权衡。K-quant方法使用混合精度
     (某些层获得的比特数多于其他层)。2.4GB模型可在
     任何消费级设备上运行。

══════════════════════════════════════════════════════════════

Back Donate