[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"consumer-news-detail-838":3,"consumer-news-interaction-838":41,"consumer-news-related-838":44},{"detail":4,"item":36},{"card":5,"schemaVersion":23,"fields":24,"content":30},{"id":6,"kind":7,"targetType":8,"targetId":9,"subtype":7,"typeLabel":10,"title":11,"subtitle":12,"summary":13,"coverUrl":14,"badgeText":15,"href":16,"sourceName":12,"meta":17,"metrics":20,"tags":21,"resolved":22},"NEWS_ARTICLE:838","news","NEWS_ARTICLE",838,"资讯","AI 学习笔记：LLM 的微调实验","博客园","title: LLM 的微调实验 author: 凌杰 date: 2026-08-21 tags: LoRA, LLaMA-Factory, Qwen categories: 人工智能 [!NOTE] 笔记说明 这篇笔记对应的是《[[关于 AI 的学习路线图]]》一文中所规划的第三个学习阶段。其中","https:\u002F\u002Fimg2024.cnblogs.com\u002Fblog\u002F691082\u002F202609\u002F691082-20260902122904258-1285666483.png","","\u002Fnews\u002F838",[18,19],"2026","综合技术",{},[19],true,"consumer-content-detail-v1",{"sourceName":12,"authorName":25,"categoryName":19,"summary":13,"description":13,"publishTime":26,"updateTime":27,"sourceUrl":28,"language":29},"凌杰","2026-09-02T12:29","2026-09-02T20:37:48","https:\u002F\u002Fwww.cnblogs.com\u002Fowlman\u002Fp\u002F22806036","中文",{"format":31,"policy":32,"normalized":22,"html":33,"text":34,"wordCount":35,"hasBody":22},"HTML","NEWS_CONTENT_V1","\u003Cblockquote>\n \u003Cp>[!NOTE] 笔记说明\u003C\u002Fp>\n \u003Cp>这篇笔记对应的是《[[关于 AI 的学习路线图]]》一文中所规划的第三个学习阶段。其中记录了我尝试在个人开发环境中对一款轻量型的 LLM 进行微调的实验过程，以及个人在该过程中所获得的心得体会。同样的，这些内容也将成为我 AI 系列笔记的一部分，被存储在本人 Github 上的\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fowlman\u002FCS_StudyNotes\" target=\"_blank\" rel=\"noopener noreferrer nofollow\">计算机学习笔记库\u003C\u002Fa>中，并予以长期维护。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>在正式开始实验之前，我首先需要与读者建立一些约定，用于确保这篇笔记所记录的实验过程与相关内容能真正发挥它的作用。由于这个实验的目的是通过实际操作过程来观察 LLM 微调的实际作用，并进而观察这项技术在 AI 系统中所扮演的角色，所以我认为实验环境应该是面向普通的个人开发者的。换言之，这篇笔记应该记录的是基于普通笔记本电脑或 Mini PC 这类设备环境所进行的实验，它们通常没有独立显卡，基本配置如图 1 所示。这意味着，我在实验中只能使用 CPU 版本的微调工具环境，针对\u003Ccode>Qwen 2.5-0.5B\u003C\u002Fcode>这种微小规模的 LLM，\u003Cstrong>进行力所能及的基础微调实践\u003C\u002Fstrong>。\u003C\u002Fp>\n\u003Cp>\u003Cimg alt=\"图 1 这篇笔记所使用的硬件环境\" src=\"https:\u002F\u002Fi1.wp.com\u002Fimg2024.cnblogs.com\u002Fblog\u002F691082\u002F202609\u002F691082-20260902122904258-1285666483.png?w=720&amp;quality=65&amp;strip=all\">\u003C\u002Fp>\n\u003Cp>\u003Cstrong>图 1\u003C\u002Fstrong> 这篇笔记所使用的硬件环境\u003C\u002Fp>\n\u003Cp>正因如此，对于我在这篇笔记中的所有观察以及得出的结论，读者应将其严格限定在 ‘消费级 CPU 硬件 + 小参数量模型’的范畴内，不涉及分布式训练或大模型全量微调。这既是这篇笔记的局限所在，也是实验最真实的参考坐标。\u003C\u002Fp>\n\u003Cblockquote>\n \u003Cp>[!WARNING] 友情提示\u003C\u002Fp>\n \u003Cp>如果不是出于体验流程、理解其工作方式的实验目的，强烈建议读者不要像我一样在一台没有独立显卡的设备上运行 LLM 的微调。因为，在 CPU 上运行微调任务不仅有速度极慢，很容易失败等问题，而且微调的效果也不具备任何现实意义。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Ch2>知识准备\u003C\u002Fh2>\n\u003Cp>现在，让我们先来了解一下什么是 LLM 微调（Fine-tuning）。简单来说就是，在经历了我在《[[深度学习的训练与评估]]》这篇笔记中所介绍的、被称为 “预训练” 的处理之后，我们会得到一个各方面都具备一定知识的 LLM。但是，它的知识新旧取决于预训练的截止日期，知识的广度和深度取决于预训练使用的语料，以及在后续有监督微调（Supervised Fine-Tuning，以下简称 SFT）阶段引入的、由其他 LLM 通过知识蒸馏或合成生成的训练数据。所以在通常情况下，人们在将它部署为网络服务之前，都会使用一系列小批量数据做一次再训练，以便将它调整更符合某一特定市场的法律法规、用户习惯，或者更适用于它将要执行的任务类型。换言之，读者可以将 LLM 的预训练与微调的关系理解成：\u003Cstrong>预训练 = 教 LLM 学会人类的语言和世界知识；微调 = 在这个基础上对 LLM 进行针对性培训。\u003C\u002Fstrong> 举个例子，假设你有一个已经训练好的通用 LLM，在没有经过微调之前，它可能只能做下面这样的一般性问答：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>输入：解释一下什么是 HTTP\n输出：HTTP 是超文本传输协议的缩写，用于在互联网上传输数据。\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>现在，如果我们希望它能在输出回答时做一些\u003Cstrong>代码审查\u003C\u002Fstrong>，就需要准备一批像下面这样的训练数据：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>输入：\n    检查下面这段代码……\n    [Python 代码]\n\n输出：\n    1. 这里存在……\n    2. 原因是……\n    3. 建议修改为……\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>然后让 LLM 在这些数据上进行再训练，在这轮训练完成之后，它就能实现一些更偏向于代码审查任务的问答了，例如像下面这样。\u003C\u002Fp>\n\u003Cpre>\u003Ccode>输入：\n    检查下面这段 Python 代码：\n\n    ```Python\n    def load_jsonl(path):\n        \"\"\"从 jsonl 文件加载训练样本。每行一个 JSON 对象。\"\"\"\n        samples = []\n        with open(path, 'r') as f:\n            for line in f:\n                samples.append(json.loads(line.strip()))\n        return samples\n    ```\n\n输出：\n    1. 这里存在：函数没有指定文件编码，同时对每一行的 JSON 解析失败没有任何处理。\n    2. 原因是：在 Windows 平台上以默认编码打开 UTF-8 文件很容易触发 UnicodeDecodeError；而数据集只要有一行 JSON 格式损坏，整个加载流程就会立刻中断，导致后续训练无法继续。\n    3. 建议修改为：在 open() 中显式声明 encoding='utf-8'，并对 json.loads 用 try\u002Fexcept 包裹，捕获 json.JSONDecodeError 后跳过坏行。\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>需要注意的是，正确理解上面所说的 “更倾向” 这个词是非常重要的。在我们继续讨论具体的微调技术之前，先厘清一个常见的误解：微调并不是简单地给 LLM 增加一个可在运行时进行查询的数据库（那是 RAG 的工作方式，我会在《[[Agent 的记忆机制]]》这篇笔记中具体介绍它），它本质上是通过改变参数的方式，使 LLM 在面对特定输入时更容易产生人们希望得到的输出。截止到我写这篇笔记的时间（2026-08-21），市面上常用的微调技术主要有三大类，具体如表 1 所示。\u003C\u002Fp>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>大类\u003C\u002Fth>\n   \u003Cth>主要思路\u003C\u002Fth>\n   \u003Cth>代表方法\u003C\u002Fth>\n   \u003Cth>资源开销\u003C\u002Fth>\n   \u003Cth>典型适用场景\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>\u003Cstrong>全参数微调 (FFT)\u003C\u002Fstrong>\u003C\u002Ftd>\n   \u003Ctd>在下游任务上更新模型的所有参数\u003C\u002Ftd>\n   \u003Ctd>—\u003C\u002Ftd>\n   \u003Ctd>极高（与模型参数量线性相关）\u003C\u002Ftd>\n   \u003Ctd>数据中心 \u002F 多卡集群上的基础模型继续训练\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>\u003Cstrong>参数高效微调 (PEFT)\u003C\u002Fstrong>\u003C\u002Ftd>\n   \u003Ctd>冻结原模型，只更新一小部分参数或新增的可学习模块\u003C\u002Ftd>\n   \u003Ctd>LoRA \u002F QLoRA \u002F DoRA \u002F AdaLoRA \u002F LoHa \u002F LoKr \u002F Adapter \u002F IA³ 等\u003C\u002Ftd>\n   \u003Ctd>低（通常只占原参数量的不到 1%）\u003C\u002Ftd>\n   \u003Ctd>单卡 \u002F 个人开发者 \u002F 在已有基座上做领域适配\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>\u003Cstrong>偏好对齐类\u003C\u002Fstrong>\u003C\u002Ftd>\n   \u003Ctd>在 SFT 基础上进一步让模型行为对齐人类偏好\u003C\u002Ftd>\n   \u003Ctd>RLHF (PPO) \u002F DPO \u002F KTO \u002F ORPO\u003C\u002Ftd>\n   \u003Ctd>中到高\u003C\u002Ftd>\n   \u003Ctd>对齐对话模型的行为、安全、风格\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>\u003Cstrong>表 1\u003C\u002Fstrong> LLM 微调技术的主要分类\u003C\u002Fp>\n\u003Cp>在上述几类方法之中，PEFT 在过去几年间已经从一种 \"在资源受限时不得不用的妥协方案\"，逐步演变为几乎所有主流 LLM 微调工作的默认起点。而我们在这里将要用到的 LoRA 则是这一众 PEFT 方法之中，被使用得最多、被研究得最透、对生态支持最完善的那一个。下面我们就来重点介绍一下它。\u003C\u002Fp>\n\u003Ch3>LoRA 简介\u003C\u002Fh3>\n\u003Cp>LoRA 这个微调方法最早出自一篇名为 \u003Cem>\u003Ca href=\"https:\u002F\u002Farxiv.org\u002Fabs\u002F2106.09685\" target=\"_blank\" rel=\"noopener noreferrer nofollow\">LoRA: Low-Rank Adaptation of Large Language Models\u003C\u002Fa>\u003C\u002Fem> 的论文。其核心思想可以概括为一句话：\u003Cstrong>LLM 在适配下游任务时，权重矩阵的更新量 \u003Cspan>\\(\\Delta W\\)\u003C\u002Fspan> 具有较低的本征秩（intrinsic rank），因此无需更新完整的权重矩阵 \u003Cspan>\\(W\\)\u003C\u002Fspan>，只需要用两个低秩矩阵的乘积来近似 \u003Cspan>\\(\\Delta W\\)\u003C\u002Fspan> 即可\u003C\u002Fstrong>。如果将这句话翻译成更精确的数学语言来描述，那就是：对于 Transformer 中任意一个需要适配的权重矩阵 \u003Cspan>\\(W\\)\u003C\u002Fspan>（维度为 \u003Cspan>\\(d \\times k\\)\u003C\u002Fspan>），我们都可以用两个小矩阵 \u003Cspan>\\(B\\)\u003C\u002Fspan>（维度 \u003Cspan>\\(d \\times r\\)\u003C\u002Fspan>）与 \u003Cspan>\\(A\\)\u003C\u002Fspan>（维度 \u003Cspan>\\(r \\times k\\)\u003C\u002Fspan>）的乘积 \u003Cspan>\\(BA\\)\u003C\u002Fspan> 来表达 \u003Cspan>\\(\\Delta W\\)\u003C\u002Fspan>，即 \u003Cspan>\\(\\Delta W = BA\\)\u003C\u002Fspan>，其中 \u003Cspan>\\(r\\)\u003C\u002Fspan> 远小于 \u003Cspan>\\(d\\)\u003C\u002Fspan> 与 \u003Cspan>\\(k\\)\u003C\u002Fspan>（实践中通常取 4、8、16、32 这样的值），具体公式如下：\u003C\u002Fp>\n\n\u003Cdiv>\n \\[W' = W + \\Delta W = W + \\frac{\\alpha}{r} B A \\]\n\u003C\u002Fdiv>\n\n\u003Cp>在该公式中，\u003Cspan>\\(W\\)\u003C\u002Fspan> 在训练期间始终保持冻结，\u003Cspan>\\(A\\)\u003C\u002Fspan> 与 \u003Cspan>\\(B\\)\u003C\u002Fspan> 为可训练参数，\u003Cspan>\\(\\alpha\\)\u003C\u002Fspan> 是一个缩放系数（通常与 \u003Cspan>\\(r\\)\u003C\u002Fspan> 同阶），用于控制 LoRA 更新的整体强度。在推理阶段，可以直接把 \u003Cspan>\\(BA\\)\u003C\u002Fspan> 合并回 \u003Cspan>\\(W\\)\u003C\u002Fspan>，得到一个与原始模型结构完全一致的权重矩阵，因此 LoRA 不会给推理带来任何额外的延迟。\u003C\u002Fp>\n\u003Cp>当然，在那篇最早提出 LoRA 方法的论文中，作者们将 self-attention 中的 \u003Cspan>\\(W_q\\)\u003C\u002Fspan>、\u003Cspan>\\(W_k\\)\u003C\u002Fspan>、\u003Cspan>\\(W_v\\)\u003C\u002Fspan>、\u003Cspan>\\(W_o\\)\u003C\u002Fspan> 作为潜在适配目标，并在 Section 7.1 的实证研究中发现\u003Cstrong>同时适应 \u003Cspan>\\(W_q\\)\u003C\u002Fspan> 与 \u003Cspan>\\(W_v\\)\u003C\u002Fspan> 在相同参数预算下效果最佳\u003C\u002Fstrong>，这也正是 HuggingFace PEFT 库默认只把这两个矩阵设置为\u003Ccode>target_modules\u003C\u002Fcode>的原因；而在 LLaMA-Factory 等工程框架的默认配置（如本文 YAML 中实际使用的 \u003Ccode>lora_target: all\u003C\u002Fcode>）中，LoRA 适配则会进一步扩展到全部线性层，以增强在中小规模数据集上的适配稳定性。不过在后续的工程实践中，LoRA 已经被普遍推广到了前馈网络中的矩阵以及 embedding、LM head 等位置中，研究者们也沿着 \"低秩分解\" 这条主线发展出了多条改进路径：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>QLoRA\u003C\u002Fstrong>（由 Dettmers 等人于 2023 年提出，相关论文是 \u003Ca href=\"https:\u002F\u002Farxiv.org\u002Fabs\u002F2305.14314\" target=\"_blank\" rel=\"noopener noreferrer nofollow\">arXiv:2305.14314\u003C\u002Fa>）：先把用于充当基座的 LLM 量化到 4-bit NormalFloat（NF4），再在其上挂载 LoRA，从而将原本需要多卡高端 GPU 才能完成的、65B 规模的 LLM 微调，压缩到单卡 48GB GPU（如 RTX A6000 48GB、A100 40GB）即可完成的规模；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>DoRA\u003C\u002Fstrong>（由 Liu 等人于 2024 年提出，相关论文是 \u003Ca href=\"https:\u002F\u002Farxiv.org\u002Fabs\u002F2402.09353\" target=\"_blank\" rel=\"noopener noreferrer nofollow\">arXiv:2402.09353\u003C\u002Fa>）：把权重矩阵拆成幅值（magnitude）与方向（direction）两部分，其中，方向的部分沿用 LoRA 处理，幅值的部分则用一个可训练的缩放向量来表达，在许多任务上获得了比 LoRA 更好的效果；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>AdaLoRA\u003C\u002Fstrong>：自适应地为不同层、不同模块分配不同的秩 \u003Cspan>\\(r\\)\u003C\u002Fspan>；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>LoHa \u002F LoKr\u003C\u002Fstrong>：分别用低秩 Hadamard 积与低秩 Kronecker 积来表示 \u003Cspan>\\(\\Delta W\\)\u003C\u002Fspan>，进一步丰富了 \"低秩适配\" 的表达形式。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>这些方法在思路上都与 LoRA 同源，因此读者只要把 LoRA 的原理弄清楚了，再去理解后续的改进工作就会顺畅很多。\u003C\u002Fp>\n\u003Ch3>为什么选择 LoRA\u003C\u002Fh3>\n\u003Cp>现在，让我们回到接下来要进行的实验设定上了。根据我们在笔记开篇时的硬件约定，实验使用的是普通笔记本电脑或 Mini PC 这类基本没有独立显卡的设备。这就意味着，设备的\u003Cstrong>显存与内存容量\u003C\u002Fstrong>是制约我们能跑多大参数的 LLM、能训练多久的主要瓶颈。在这种情况下，LoRA 在资源开销上所具备的以下这几项优势，让它几乎成为了我们进行这个实验的唯一选择。\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>可训练参数大幅减少\u003C\u002Fstrong>：LoRA 方法需要训练的参数量通常都只有 LLM 全部参数的 1% 以下，这就直接降低了 Adam 等优化器所需要维护的状态（动量、方差等）的存储开销；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>激活显存与梯度显存显著下降\u003C\u002Fstrong>：在 LoRA 方法中，被冻结的 \u003Cspan>\\(W\\)\u003C\u002Fspan> 在反向传播中不需要保存梯度，反向传播也只在小矩阵 \u003Cspan>\\(A\\)\u003C\u002Fspan>、\u003Cspan>\\(B\\)\u003C\u002Fspan> 上进行；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>训练产物小巧且易于管理\u003C\u002Fstrong>：一次训练只产出一个体积通常只有几十到几百 MB 的 adapter 文件，便于在不同数据集、不同超参配置下进行多轮实验，并按需切换、叠加；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>生态成熟\u003C\u002Fstrong>：Hugging Face PEFT、LLaMA-Factory 等主流的 LLM 微调框架都对 LoRA 方法提供了完善的支持，从命令行工具到 WebUI 都有现成的封装。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>具体到本实验的目标对象\u003Ccode>Qwen 2.5-0.5B\u003C\u002Fcode>，它的参数量虽然不大，但即使是 0.5B 这种轻量级模型，全量微调在 CPU 环境下仍然会占用相当可观的内存与计算资源，训练时长也往往会拉长到令人难以接受的水平；而改用 LoRA 之后，单次训练的内存峰值与时长都能降到本实验硬件可以承受的范围内，训练产物也便于我们在后续阶段对它进行二次评估与对比。\u003C\u002Fp>\n\u003Cp>最后，还需要说明的是，我在这次实验中之所以选择 LoRA 而不是 QLoRA、DoRA 等更新的改进方法，主要是因为 LoRA 是整个 LoRA 家族中最为经典、对 CPU 环境最友好、社区资料最丰富的那一种，对一名初学者来说，它是理解 \"PEFT\" 这条技术路线最合适的起点。\u003C\u002Fp>\n\u003Ch2>实验过程记录\u003C\u002Fh2>\n\u003Cp>在完成了上述知识准备之后，现在让我们正式开始 LLM 的微调实验吧，其具体步骤如下。\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cp>安装 uv 并创建一个基于 Python 3.12 的虚拟环境。关于这部分的操作方法，我已经在《[[编程环境配置|Python 学习笔记：编程环境配置]]》（\u003Ca href=\"https:\u002F\u002Fwww.cnblogs.com\u002Fowlman\u002Fp\u002F19501012\" target=\"_blank\" rel=\"noopener noreferrer nofollow\">博客园链接\u003C\u002Fa>）这篇笔记中做过详细介绍，这里就不再赘述了。总而言之，在完成这些操作之后，我们会得到如图 2 所示的结果。\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"图 2 虚拟环境创建成功\" src=\"https:\u002F\u002Fi1.wp.com\u002Fimg2024.cnblogs.com\u002Fblog\u002F691082\u002F202609\u002F691082-20260902122905125-1497709744.png?w=720&amp;quality=65&amp;strip=all\">\u003C\u002Fp>\u003Cp>\u003Cstrong>图 2\u003C\u002Fstrong> 创建基于 Python 3.12 的虚拟环境\u003C\u002Fp>\u003C\u002Fli>\n \u003Cli>\u003Cp>基于《论语》这本书的文本准备一个极小规模的微调数据集，这可以使用 OpenCode 这样的 Agent 工具帮我们自动生成，提示词为：\u003Cem>“我需要基于 LLaMA-Factory 来进行一次 LLM 微调实验，帮我生成一份基于《论语》的微调数据集，数据集格式为 JSON Lines，每条数据包含一个问题和对应的答案，数据集大小为 10 条。”\u003C\u002Fem> 具体如图 3 所示。\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"图 3 OpenCode 自动生成微调数据集\" src=\"https:\u002F\u002Fi1.wp.com\u002Fimg2024.cnblogs.com\u002Fblog\u002F691082\u002F202609\u002F691082-20260902122905587-2085656775.png?w=720&amp;quality=65&amp;strip=all\">\u003C\u002Fp>\u003Cp>\u003Cstrong>图 3\u003C\u002Fstrong> OpenCode 自动生成微调数据集\u003C\u002Fp>\u003C\u002Fli>\n \u003Cli>\u003Cp>安装 LLaMA-Factory 及其 CPU 版的依赖，这需要我们打开命令行终端，并依次执行以下命令：\u003C\u002Fp>\u003Cpre>\u003Ccode># 安装 LLaMA-Factory\npip install llamafactory\n\n# 安装 CPU 版 PyTorch\npip install torch torchvision torchaudio --index-url https:\u002F\u002Fdownload.pytorch.org\u002Fwhl\u002Fcpu\n\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>如果一切顺利，待上述操作执行完成之后，我们在命令行终端中输入\u003Ccode>llamafactory-cli help\u003C\u002Fcode>，应该会看到如图 4 所示的结果。\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"图 4 LLaMA Factory 帮助信息\" src=\"https:\u002F\u002Fi1.wp.com\u002Fimg2024.cnblogs.com\u002Fblog\u002F691082\u002F202609\u002F691082-20260902122906163-1540124407.png?w=720&amp;quality=65&amp;strip=all\">\u003C\u002Fp>\u003Cp>\u003Cstrong>图 4\u003C\u002Fstrong> LLaMA Factory 帮助信息\u003C\u002Fp>\u003C\u002Fli>\n \u003Cli>\u003Cp>配置并运行 CPU 版的 LoRA 微调，这需要我们执行\u003Ccode>llamafactory-cli webui\u003C\u002Fcode>命令，打开 LLaMA-Factory 的 Web 操作界面，并按照图 5 所示的步骤，选择我们接下来要使用的 LLM 权重文件与微调数据集。\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"图 5 LLaMA Factory界面\" src=\"https:\u002F\u002Fi1.wp.com\u002Fimg2024.cnblogs.com\u002Fblog\u002F691082\u002F202609\u002F691082-20260902122906435-2021764521.png?w=720&amp;quality=65&amp;strip=all\">\u003C\u002Fp>\u003Cp>\u003Cstrong>图 5\u003C\u002Fstrong> LLaMA-Factory 的 Web 操作界面\u003C\u002Fp>\u003C\u002Fli>\n \u003Cli>\u003Cp>默认情况下，LLaMA-Factory 的最新版本会自行从 Hugging Face 或 ModelScope 平台上获取到我们在上述界面中指定的 LLM 权重文件，但如果是使用旧版，且无法连接 Hugging Face 的用户，那就得亲自去 ModelScope 网站上搜索到\u003Ccode>Qwen 2.5-0.5B\u003C\u002Fcode>，并按照图 6 中所示的步骤下载权重文件。\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"图 6 找到 Qwen 2.5-0.5B 权重文件\" src=\"https:\u002F\u002Fi1.wp.com\u002Fimg2024.cnblogs.com\u002Fblog\u002F691082\u002F202609\u002F691082-20260902122906854-1237614173.png?w=720&amp;quality=65&amp;strip=all\">\u003C\u002Fp>\u003Cp>\u003Cstrong>图 6\u003C\u002Fstrong> 找到 Qwen 2.5-0.5B 权重文件\u003C\u002Fp>\u003Cp>其具体操作过程如图 7 所示。读者可以注意到，待\u003Ccode>Qwen 2.5-0.5B\u003C\u002Fcode>的权重文件下载完成之后，ModelScope 的下载器会自动告诉我们文件的存储位置，如果需要的话，我们也可以将它另存到一个比较方便管理的位置上。然后再将该位置填写到图 5 所示界面的 “模型路径” 字段中，这样，我们就可以在后续的微调过程中使用本地的权重文件了。\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"图 7 下载 Qwen 2.5-0.5B 权重文件\" src=\"https:\u002F\u002Fi1.wp.com\u002Fimg2024.cnblogs.com\u002Fblog\u002F691082\u002F202609\u002F691082-20260902122907173-1049104983.png?w=720&amp;quality=65&amp;strip=all\">\u003C\u002Fp>\u003Cp>\u003Cstrong>图 7\u003C\u002Fstrong> 下载 Qwen 2.5-0.5B 权重文件\u003C\u002Fp>\u003C\u002Fli>\n \u003Cli>\u003Cp>按照图 3 中 OpenCode 给出的提示，将它替我们准备好的数据集注册到 LLaMA-Factory 中，并将该数据集命名为\u003Ccode>lunyu_sample\u003C\u002Fcode>，然后在刷新 Web 界面之后，我们就可以顺利将该数据集加载到 LLaMA-Factory 中了，如图 8 所示。\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"图 8 注册并加载微调数据集\" src=\"https:\u002F\u002Fi1.wp.com\u002Fimg2024.cnblogs.com\u002Fblog\u002F691082\u002F202609\u002F691082-20260902122907454-94286375.png?w=720&amp;quality=65&amp;strip=all\">\u003C\u002Fp>\u003Cp>\u003Cstrong>图 8\u003C\u002Fstrong> 注册并加载微调数据集\u003C\u002Fp>\u003C\u002Fli>\n \u003Cli>\u003Cp>由于我们执行的是 CPU 版的 LoRA 微调，因此需要按照表 2 所示的配置，在 LLaMA Factory 的 Web 界面中设置训练参数。\u003C\u002Fp>\n  \u003Ctable>\n   \u003Cthead>\n    \u003Ctr>\n     \u003Cth>参数\u003C\u002Fth>\n     \u003Cth>本实验使用值\u003C\u002Fth>\n     \u003Cth>相关说明\u003C\u002Fth>\n    \u003C\u002Ftr>\n   \u003C\u002Fthead>\n   \u003Ctbody>\n    \u003Ctr>\n     \u003Ctd>\u003Ccode>finetuning_type\u003C\u002Fcode>\u003C\u002Ftd>\n     \u003Ctd>\u003Ccode>lora\u003C\u002Fcode>\u003C\u002Ftd>\n     \u003Ctd>全参数微调要更新 LLM 里几亿个参数，CPU 上既吃内存又慢到没法等；LoRA 只额外训练几个小矩阵、其余全部冻结，是 CPU 上唯一现实的选择\u003C\u002Ftd>\n    \u003C\u002Ftr>\n    \u003Ctr>\n     \u003Ctd>\u003Ccode>model_name_or_path\u003C\u002Fcode>\u003C\u002Ftd>\n     \u003Ctd>\u003Ccode>Qwen\u002FQwen2.5-0.5B\u003C\u002Fcode>\u003C\u002Ftd>\n     \u003Ctd>没独立显卡时，显存就是内存。LLM 越大，加载占用的内存和每一步的计算量就越大；0.5B 是这个条件下既能跑起来、又有基本智能的最小模型之一\u003C\u002Ftd>\n    \u003C\u002Ftr>\n    \u003Ctr>\n     \u003Ctd>\u003Ccode>bf16\u003C\u002Fcode> \u002F \u003Ccode>fp16\u003C\u002Fcode>\u003C\u002Ftd>\n     \u003Ctd>\u003Ccode>false\u003C\u002Fcode> \u002F \u003Ccode>false\u003C\u002Fcode>\u003C\u002Ftd>\n     \u003Ctd>bf16\u002Ffp16 是给 GPU 的半精度加速玩法：在 CPU 上要么直接不支持（报错 \"expected scalar type Float but found Half\"），要么精度损失让训练结果变差；保持两者均为 false 即走 fp32，最稳，慢一点但不会翻车\u003C\u002Ftd>\n    \u003C\u002Ftr>\n    \u003Ctr>\n     \u003Ctd>\u003Ccode>cutoff_len\u003C\u002Fcode>\u003C\u002Ftd>\n     \u003Ctd>\u003Ccode>256\u003C\u002Fcode>\u003C\u002Ftd>\n     \u003Ctd>序列越长，单步计算量越大，CPU 就越慢；Qwen2.5 虽支持 3 万 token 长上下文，但《论语》问答样本最长也不超过 200 token，主动截到 256 能省大量时间，效果几乎不受影响\u003C\u002Ftd>\n    \u003C\u002Ftr>\n    \u003Ctr>\n     \u003Ctd>\u003Ccode>batch_size\u003C\u002Fcode>\u003C\u002Ftd>\n     \u003Ctd>\u003Ccode>2\u003C\u002Fcode>\u003C\u002Ftd>\n     \u003Ctd>GPU 一次能并行处理几十条样本，CPU 上只能串行；本实验实测将 batch_size 设为 2 时，6 核 CPU 的负载比较均衡，单步耗时比 1 反而更短\u003C\u002Ftd>\n    \u003C\u002Ftr>\n    \u003Ctr>\n     \u003Ctd>\u003Ccode>gradient_accumulation_steps\u003C\u002Fcode>\u003C\u002Ftd>\n     \u003Ctd>\u003Ccode>2\u003C\u002Fcode>\u003C\u002Ftd>\n     \u003Ctd>batch 太小会让梯度噪声大、训练不稳；本实验的有效 batch size 为 2×2=4（小数据集下 GA 设大反而拖慢训练且不改善 loss 曲线）\u003C\u002Ftd>\n    \u003C\u002Ftr>\n    \u003Ctr>\n     \u003Ctd>\u003Ccode>learning_rate\u003C\u002Fcode>\u003C\u002Ftd>\n     \u003Ctd>\u003Ccode>1e-4\u003C\u002Fcode>\u003C\u002Ftd>\n     \u003Ctd>LoRA 只更新少量参数，本质就是在已有的 LLM 上轻轻推一把，所以可以用比全参数微调更大的学习率；本实验取推荐区间下界 1e-4，配合极小数据集更稳妥\u003C\u002Ftd>\n    \u003C\u002Ftr>\n   \u003C\u002Ftbody>\n  \u003C\u002Ftable>\u003Cp>\u003Cstrong>表 2\u003C\u002Fstrong> CPU 版 LoRA 微调的参数配置\u003C\u002Fp>\u003C\u002Fli>\n \u003Cli>\u003Cp>在完成上述参数设置之后，我们就可以点击页面中的 “开始” 按钮来启动 LoRA 微调了，如图 9 所示。\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"图 9 开始 LoRA 微调\" src=\"https:\u002F\u002Fi1.wp.com\u002Fimg2024.cnblogs.com\u002Fblog\u002F691082\u002F202609\u002F691082-20260902122907733-1488012362.png?w=720&amp;quality=65&amp;strip=all\">\u003C\u002Fp>\u003Cp>\u003Cstrong>图 9\u003C\u002Fstrong> 开始 LoRA 微调\u003C\u002Fp>\u003Cp>\u003Cstrong>补充：等价的命令行方式\u003C\u002Fstrong>。上面的 WebUI 操作也可以通过一份 YAML + 一行命令来等价复现，便于脚本化或在 SSH 远程机上运行：\u003C\u002Fp>\u003Cpre>\u003Ccode>llamafactory-cli train train_lunyu_sample_fast.yaml\n\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>其中 \u003Ccode>train_lunyu_sample_fast.yaml\u003C\u002Fcode> 中的主要参数设定与表 2 一一对应，具体如下：\u003C\u002Fp>\u003Cpre>\u003Ccode>model_name_or_path: Qwen\u002FQwen2.5-0.5B      # 模型名称\ndataset: lunyu_sample                      # 数据集名称\ndataset_dir: data                          # 数据集存储路径\ntemplate: qwen                             # 模板名称\nfinetuning_type: lora                      # 微调类型\nlora_rank: 8                               # LoRA 矩阵的秩\nlora_alpha: 16                             # LoRA 矩阵的缩放因子\nlora_dropout: 0                            # LoRA 矩阵的丢弃率\nlora_target: all                           # LoRA 矩阵的目标层\nstage: sft                                 # 微调阶段\ndo_train: true                             # 是否进行训练\noutput_dir: saves\\Qwen2.5-0.5B\\lora\\train_lunyu_sample_fast # 输出目录\noverwrite_output_dir: true                 # 是否覆盖输出目录\ncutoff_len: 256                            # 截断长度\nper_device_train_batch_size: 2             # 每个设备的训练批次大小\ngradient_accumulation_steps: 2             # 梯度累积步数\nlearning_rate: 0.0001                      # 学习率\nnum_train_epochs: 3.0                      # 训练轮数\nlr_scheduler_type: cosine                  # 学习率调度器类型\nwarmup_steps: 0                            # 预热步数\nmax_grad_norm: 1.0                         # 最大梯度范数\nlogging_steps: 1                           # 日志记录步数\nsave_steps: 6                              # 保存步数\nplot_loss: true                            # 是否绘制损失曲线\npreprocessing_num_workers: 6               # 预处理工作线程数\ndataloader_num_workers: 4                  # 数据加载工作线程数\nflash_attn: auto                           # 是否使用 Flash Attention\nbf16: false                                # 是否使用 bf16\nfp16: false                                # 是否使用 fp16\ngradient_checkpointing: false              # 是否使用梯度检查点\noptim: adamw_torch                         # 优化器类型\npacking: false                             # 是否使用打包\nenable_thinking: false                     # 是否启用思考\nreport_to: none                            # 报告类型\ntrust_remote_code: true                    # 是否信任远程代码\nseed: 42                                   # 随机种子\n\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>这份 YAML 与 WebUI 表单的最大差异在于：它可被纳入到 Git 版本控制系统中进行管理，这是当前任务做成 \"可复现实验\" 的关键一步。后续如果我们的设备条件允许，需要换成\u003Ccode>Qwen2.5-1.5B\u003C\u002Fcode>或切换到\u003Ccode>Qwen3-1.7B\u003C\u002Fcode>，只需要修改\u003Ccode>model_name_or_path\u003C\u002Fcode>和\u003Ccode>cutoff_len\u003C\u002Fcode>，其他参数都不用动，就能复现同样的实验结果。\u003C\u002Fp>\u003C\u002Fli>\n \u003Cli>\u003Cp>待训练开始之后，我们就能在 LLaMA-Factory 的 Web 界面的底部看到训练过程的实时输出框了，并且在训练持续一段时间之后，训练进度条也会在实时输出框的上方出现，如图 10 所示。由于我们使用的纯 CPU 的微调方式，这个过程会非常慢，基本上都要持续 2-5 个小时不等，且失败几率很高。\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"图 10 LoRA 微调训练进度\" src=\"https:\u002F\u002Fi1.wp.com\u002Fimg2024.cnblogs.com\u002Fblog\u002F691082\u002F202609\u002F691082-20260902122907989-1656931121.png?w=720&amp;quality=65&amp;strip=all\">\u003C\u002Fp>\u003Cp>\u003Cstrong>图 10\u003C\u002Fstrong> LoRA 微调训练进度\u003C\u002Fp>\u003C\u002Fli>\n \u003Cli>\u003Cp>一旦训练完成，我们就会在 LLaMA-Factory 的 Web 界面中看到上面的训练进度条走到了 100%。同时，”中断“ 按钮的下方会出现一张完整的 loss 曲线图，如图 11 所示。\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"图 11 loss 曲线图\" src=\"https:\u002F\u002Fi1.wp.com\u002Fimg2024.cnblogs.com\u002Fblog\u002F691082\u002F202609\u002F691082-20260902145916153-1820382685.png?w=720&amp;quality=65&amp;strip=all\">\u003C\u002Fp>\u003Cp>\u003Cstrong>图 11\u003C\u002Fstrong> ”中断“ 按钮下方出现的 loss 曲线图\u003C\u002Fp>\u003Cp>至于训练的具体成果究竟如何，我们可以从以下几个关键信号来判断：\u003C\u002Fp>\n  \u003Cul>\n   \u003Cli>\u003Cstrong>loss 曲线持续下降并最终趋于平稳\u003C\u002Fstrong>：\u003Ccode>train_loss\u003C\u002Fcode> 在前几个 epoch 出现明显下降（起步通常在 1.5~3.0 区间，收敛后稳定在 0.1~0.5）；\u003Ccode>eval_loss\u003C\u002Fcode> 与 \u003Ccode>train_loss\u003C\u002Fcode> 趋势一致（若 \u003Ccode>train_loss\u003C\u002Fcode> 下降而 \u003Ccode>eval_loss\u003C\u002Fcode> 上升，则视为过拟合）。一个粗略的经验阈值：\u003Ccode>train_loss\u003C\u002Fcode> &gt; 1.0 多为欠拟合，&lt; 0.3 可视为基本收敛。\u003C\u002Fli>\n   \u003Cli>\u003Cstrong>grad_norm 稳定在 0.1-10 之间\u003C\u002Fstrong>：如果突然飙升到 100+ 或者出现 NaN，说明训练崩溃，需要回退学习率或检查数据。\u003C\u002Fli>\n   \u003Cli>\u003Cstrong>没有 OOM \u002F RuntimeError\u003C\u002Fstrong>：CPU 训练里常见的报错包括 \"DataLoader worker (pid X) is killed by signal: Out of memory\"（需减小\u003Ccode>batch_size\u003C\u002Fcode>）以及 \"expected scalar type Float but found Half\"（即 PyTorch CPU 版本与 bf16 不兼容，需切回 fp32）。\u003C\u002Fli>\n   \u003Cli>\u003Cstrong>总时长在 2-5 小时之间\u003C\u002Fstrong>：如果超过 5 小时，说明训练过程可能卡住；如果低于 2 小时，说明训练过程可能过快，需要检查是否有数据问题。\u003C\u002Fli>\n  \u003C\u002Ful>\u003Cp>当以上信号都出现时，就基本可以确认 LoRA 微调已经成功完成。下一步，我们就可以进入实验结果的分析环节了。\u003C\u002Fp>\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2>实验结果与分析\u003C\u002Fh2>\n\u003Cp>完成上述 10 个步骤后，整个实验就基本跑完了。需要说明的是，根据《[[关于 AI 的学习路线图]]》第三阶段的规划，我进行这个实验的核心任务是要通过 LLM 在微调前后的行为差异，来观察 \"当训练数据和优化目标发生变化后，LLM 的行为如何变化\" —— 也就是回答我在路线图第三阶段中提出的两个核心问题：\u003C\u002Fp>\n\u003Col>\n \u003Cli>LLM 在 AI 系统中是核心决策单元，还是能力增强模块？\u003C\u002Fli>\n \u003Cli>哪些是 LLM 本身能力的问题，哪些是 AI 系统的设计问题？\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>接下来，我会先把\u003Cstrong>实测的训练过程与产物数据\u003C\u002Fstrong>展开，再用这些数据回答路线图的这两个核心问题。\u003C\u002Fp>\n\u003Ch3>实际观察到的结果\u003C\u002Fh3>\n\u003Cp>首先，让我们先把之前在步骤 10 中所列的实验成功信号及其生成的 loss 曲线一并展开，然后再结合训练输出目录里的真实产物，逐项用数据来说话。\u003C\u002Fp>\n\u003Ch4>训练日志维度：4 个成功信号逐条核对\u003C\u002Fh4>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>信号\u003C\u002Fth>\n   \u003Cth>阈值\u003C\u002Fth>\n   \u003Cth>实测\u003C\u002Fth>\n   \u003Cth>结论\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>1. loss 曲线下降并趋于平稳\u003C\u002Ftd>\n   \u003Ctd>下降\u003C\u002Ftd>\n   \u003Ctd>2.07 → 1.73（波动下降，幅度约 16%）\u003C\u002Ftd>\n   \u003Ctd>部分通过（有波动）\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>2. grad_norm 稳定在 0.1-10 区间\u003C\u002Ftd>\n   \u003Ctd>区间\u003C\u002Ftd>\n   \u003Ctd>1.87 – 3.44\u003C\u002Ftd>\n   \u003Ctd>通过\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>3. 无 OOM \u002F RuntimeError\u003C\u002Ftd>\n   \u003Ctd>无报错\u003C\u002Ftd>\n   \u003Ctd>无\u003C\u002Ftd>\n   \u003Ctd>通过\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>4. 总时长 2-5 小时\u003C\u002Ftd>\n   \u003Ctd>区间\u003C\u002Ftd>\n   \u003Ctd>\u003Cstrong>16,220 秒 ≈ 4 小时 30 分\u003C\u002Fstrong>\u003C\u002Ftd>\n   \u003Ctd>通过\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>4 个信号里 3 个完全通过、1 个部分通过。这说明这次实验\u003Cstrong>在工程层面确实跑通了\u003C\u002Fstrong>，只是 loss 曲线未完全平稳收敛（这是数据集太小所致，我稍后会做详细说明）。\u003C\u002Fp>\n\u003Ch4>loss 曲线的 9 个数据点\u003C\u002Fh4>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>step\u003C\u002Fth>\n   \u003Cth>loss\u003C\u002Fth>\n   \u003Cth>grad_norm\u003C\u002Fth>\n   \u003Cth>learning_rate\u003C\u002Fth>\n   \u003Cth>epoch\u003C\u002Fth>\n   \u003Cth>备注\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>1\u003C\u002Ftd>\n   \u003Ctd>2.0652\u003C\u002Ftd>\n   \u003Ctd>3.028\u003C\u002Ftd>\n   \u003Ctd>1.00e-4\u003C\u002Ftd>\n   \u003Ctd>0.40\u003C\u002Ftd>\n   \u003Ctd>起步\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>2\u003C\u002Ftd>\n   \u003Ctd>1.9221\u003C\u002Ftd>\n   \u003Ctd>2.160\u003C\u002Ftd>\n   \u003Ctd>9.70e-5\u003C\u002Ftd>\n   \u003Ctd>0.80\u003C\u002Ftd>\n   \u003Ctd>\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>3\u003C\u002Ftd>\n   \u003Ctd>1.7187\u003C\u002Ftd>\n   \u003Ctd>3.435\u003C\u002Ftd>\n   \u003Ctd>8.83e-5\u003C\u002Ftd>\n   \u003Ctd>1.00\u003C\u002Ftd>\n   \u003Ctd>第一轮结束\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>4\u003C\u002Ftd>\n   \u003Ctd>1.7484\u003C\u002Ftd>\n   \u003Ctd>1.966\u003C\u002Ftd>\n   \u003Ctd>7.50e-5\u003C\u002Ftd>\n   \u003Ctd>1.40\u003C\u002Ftd>\n   \u003Ctd>\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>5\u003C\u002Ftd>\n   \u003Ctd>\u003Cstrong>1.9943\u003C\u002Fstrong>\u003C\u002Ftd>\n   \u003Ctd>2.571\u003C\u002Ftd>\n   \u003Ctd>5.87e-5\u003C\u002Ftd>\n   \u003Ctd>1.80\u003C\u002Ftd>\n   \u003Ctd>\u003Cstrong>反弹\u003C\u002Fstrong>\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>6\u003C\u002Ftd>\n   \u003Ctd>\u003Cstrong>1.4943\u003C\u002Fstrong>\u003C\u002Ftd>\n   \u003Ctd>2.298\u003C\u002Ftd>\n   \u003Ctd>4.13e-5\u003C\u002Ftd>\n   \u003Ctd>2.00\u003C\u002Ftd>\n   \u003Ctd>\u003Cstrong>谷值\u003C\u002Fstrong>\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>7\u003C\u002Ftd>\n   \u003Ctd>1.5658\u003C\u002Ftd>\n   \u003Ctd>2.037\u003C\u002Ftd>\n   \u003Ctd>2.50e-5\u003C\u002Ftd>\n   \u003Ctd>2.40\u003C\u002Ftd>\n   \u003Ctd>\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>8\u003C\u002Ftd>\n   \u003Ctd>1.7383\u003C\u002Ftd>\n   \u003Ctd>1.872\u003C\u002Ftd>\n   \u003Ctd>1.17e-5\u003C\u002Ftd>\n   \u003Ctd>2.80\u003C\u002Ftd>\n   \u003Ctd>\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>9\u003C\u002Ftd>\n   \u003Ctd>1.7325\u003C\u002Ftd>\n   \u003Ctd>2.677\u003C\u002Ftd>\n   \u003Ctd>3.02e-6\u003C\u002Ftd>\n   \u003Ctd>3.00\u003C\u002Ftd>\n   \u003Ctd>最终\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>可以从曲线上看到几个关键特征：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>起步 loss = 2.07\u003C\u002Fstrong>：在我之前在步骤 10 中给出的经验起点 1.5-3.0 区间内，符合预期。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>波动下降\u003C\u002Fstrong>：loss 没有单调下降。step 4→5 反弹到 1.99、step 7→8 反弹到 1.74、step 8→9 反弹到 1.73。这种波动在 9 个 step 的小样本训练里是正常的，主要由 cosine schedule 的 learning_rate 衰减曲线驱动 —— 当 learning_rate 衰减到接近 0 的尾段，loss 反而会反弹一小段。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>最终 loss = 1.73\u003C\u002Fstrong>：按经验阈值（&gt; 1.0 多为欠拟合），\u003Cstrong>严格来说这次训练仍属欠拟合\u003C\u002Fstrong>——但这是数据集太小（10 条）而非方法问题，下面\"失败模式\"小节会再展开。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>grad_norm 全程 1.87-3.44\u003C\u002Fstrong>：远低于 100+ 的崩溃阈值，全程在 0.1-10 的健康区间内，证明训练过程稳定。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch4>训练产物：output_dir 里到底有什么\u003C\u002Fh4>\n\u003Cpre>\u003Ccode>saves\u002FQwen2.5-0.5B\u002Flora\u002Ftrain_2026-09-02-10-24-13\u002F\n├── adapter_model.safetensors (17.6 MB)       # LoRA 适配器权重（核心产物）\n├── adapter_config.json (1.1 KB)              # LoRA 适配器配置\n├── chat_template.jinja (2.4 KB)              # Qwen2.5 对话模板\n├── tokenizer.json (10.9 MB)                  # tokenizer 配置（推理用）\n├── tokenizer_config.json (0.7 KB)            # tokenizer 元信息\n├── all_results.json (0.2 KB)                 # 最终汇总指标\n├── train_results.json (0.2 KB)               # 训练结果指标\n├── trainer_state.json (3.6 KB)               # 训练状态 + 9 条 loss 原始数据\n├── trainer_log.jsonl (2.2 KB)                # 逐 step 日志\n├── training_loss.png (34.5 KB)               # loss 曲线图（对应图 11）\n├── training_args.yaml (0.8 KB)               # 训练参数快照\n├── training_args.bin (5.8 KB)                # 训练参数二进制\n├── README.md (1.6 KB)                        # PEFT 自动生成的 model card\n├── llamaboard_config.yaml (2.4 KB)           # LLaMA-Factory WebUI 配置快照\n├── running_log.txt (15.1 KB)                 # 完整运行日志\n└── checkpoint-9\u002F                             # 第 9 步中间检查点（含 optimizer.pt \u002F rng_state.pth \u002F scheduler.pt）\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>最关键的产物是 \u003Ccode>adapter_model.safetensors\u003C\u002Fcode> —— 17.6 MB 的 LoRA 适配器权重，可以独立于基座模型加载，与原始的 \u003Ccode>Qwen 2.5-0.5B\u003C\u002Fcode> 模型叠加使用实现微调效果。\u003C\u002Fp>\n\u003Cp>\u003Cstrong>数据来源说明\u003C\u002Fstrong>：这里的 9 个 loss 数据点、grad_norm 范围 1.87-3.44、最终 loss 1.7325、训练时长 16,220 秒（约 4 小时 30 分 20 秒）、\u003Ccode>num_input_tokens_seen 5,488\u003C\u002Fcode> 等数字均直接取自该目录下的 \u003Ccode>all_results.json\u003C\u002Fcode>（最终汇总指标）、\u003Ccode>train_results.json\u003C\u002Fcode>、\u003Ccode>trainer_state.json\u003C\u002Fcode>（loss 曲线原始数据）、\u003Ccode>trainer_log.jsonl\u003C\u002Fcode>（逐 step 日志）等文件。这 4 个文件均已随本笔记一并入库到 \u003Ccode>examples\u002Fllama_factory\u002Fsaves\u002FQwen2.5-0.5B\u002Flora\u002Ftrain_2026-09-02-10-24-13\u002F\u003C\u002Fcode>，如需对实验数据做进一步分析或绘制其他曲线，可直接读取这些原始文件，无需重新跑训练。\u003C\u002Fp>\n\u003Ch4>adapter 的实际 target_modules\u003C\u002Fh4>\n\u003Cp>\u003Ccode>adapter_config.json\u003C\u002Fcode> 里的 \u003Ccode>target_modules\u003C\u002Fcode> 字段展开后是：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>[\"q_proj\", \"k_proj\", \"v_proj\", \"o_proj\", \"gate_proj\", \"up_proj\", \"down_proj\"]\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>这正是 \u003Ccode>lora_target: all\u003C\u002Fcode> 在 LLaMA-Factory WebUI 下的实际展开 —— Transformer 解码器层的全部 7 个线性层（注意力的 Q\u002FK\u002FV\u002FO + FFN 的 gate\u002Fup\u002Fdown）。这也印证了前文关于\"LLaMA-Factory 默认会扩展到全部线性层\"的描述。\u003C\u002Fp>\n\u003Ch4>行为维度（基于产物的推断，非实测对比）\u003C\u002Fh4>\n\u003Cp>需要注意，这次实验没有做 \"微调前 vs 微调后\" 的对比测试。前文路线图里提到的\"通过 LLM 在微调前后的行为差异来观察模型行为如何变化\"，严格来说这次实验\u003Cstrong>只完成了 \"微调\" 半边\u003C\u002Fstrong>。至于另一半（行为对比测试）按开头 WARNING 的原则属于 \"不具备任何现实意义\" 的活动，我刻意没做。因此接下来对\"行为变化\"的所有观察都是基于训练产物与 LLaMA-Factory 经验的推导，而非实测对比：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>LLM 在《论语》问答任务上的输出格式\u003Cstrong>应该\u003C\u002Fstrong>比微调前更稳定（数据集全是统一 prompt 模板的问答对）；\u003C\u002Fli>\n \u003Cli>LLM 对\"论语\"主题知识的吸收\u003Cstrong>几乎不会有\u003C\u002Fstrong>显著提升（10 条样本本身承载的知识量极低）；\u003C\u002Fli>\n \u003Cli>想要验证行为变化，最稳妥的方式是用 \u003Ccode>adapter_model.safetensors\u003C\u002Fcode> 加载后跑一批测试 prompt，对比同 prompt 下原始模型与微调模型的输出 —— 但这属于\"推理与评估\"环节，本笔记暂不展开。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch3>失败模式与边界\u003C\u002Fh3>\n\u003Cp>呼应路线图第三阶段 \"当 LLM 输出不可靠时，AI 系统该如何处理能力的退化\" 这一关键问题，这次实验在以下几种情况下应当视为 \"未达到预期\" 或 \"需要重新设计\"：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>过拟合\u003C\u002Fstrong>：train_loss 持续下降但 eval_loss 反而上升（步骤 10 第 1 条）。这说明微调输出的 adapter 只 \"记住\" 了训练集而没有学到《论语》的语言模式。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>欠拟合\u003C\u002Fstrong>：train_loss 始终在 1.5-2.0 之间下不来。这说明可能学习率过低、rank 过小、或者数据集质量有问题。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>行为漂移\u003C\u002Fstrong>：微调后 LLM 对一般性问答的回答质量明显下降。这说明数据集分布过于狭窄（全部是《论语》），导致模型丧失了原有的 \"通识\" 能力。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>训练崩溃\u003C\u002Fstrong>：loss 出现 NaN 或 grad_norm 飙升到 100+。这说明我们需要回退学习率或检查数据标注。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>对照本次实验：上面四种失败模式\u003Cstrong>本次仅触及\"欠拟合\"一项的边缘\u003C\u002Fstrong>：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>过拟合\u003C\u002Fstrong>：本实验\u003Cstrong>没有 eval_loss\u003C\u002Fstrong>（数据集只有 10 条，根本无法划分 eval 集），过拟合判断无从谈起。这是数据集规模决定的结构性约束。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>欠拟合\u003C\u002Fstrong>：最终 loss 1.73 &gt; 1.0，从阈值角度属\"欠拟合\"。但 10 条样本 + 3 epoch 本身就是\"明知会欠拟合的实验设置\"，可接受 —— 如要缓解，应\u003Cstrong>扩展数据集\u003C\u002Fstrong>（而非调整学习率或 rank）。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>行为漂移\u003C\u002Fstrong>：未做对比测试，未观察到。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>训练崩溃\u003C\u002Fstrong>：loss 无 NaN、grad_norm 全程 1.87-3.44、未触发任何崩溃。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch3>在 AI 系统中的角色讨论\u003C\u002Fh3>\n\u003Cp>基于上述结果，我们可以从两个层面回答路线图第三阶段的核心问题：\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cp>\u003Cstrong>LLM 在 AI 系统中更接近 \"能力增强模块\" 而非 \"核心决策单元\"\u003C\u002Fstrong>：我在实验中所采用的 \"在极小数据集上做轻量级 LoRA 微调\" 这一过程，本质上是为一个通才 LLM 增加了一个\u003Cstrong>针对特定任务的小型插件\u003C\u002Fstrong>。从产物角度看，本实验的产出是一个\u003Cstrong>仅占基座模型 ~1.7% 体积（17.6 MB vs ~1 GB 0.5B 模型）的 LoRA 适配器\u003C\u002Fstrong>。这个量级的产物能做的事情只有\"小幅度改变基座模型在特定任务上的输出倾向\"，而不是\"重新训练模型\"。很显然，这样做没有让\u003Ccode>Qwen 2.5-0.5B\u003C\u002Fcode>这个 LLM 变成 \"国学大师\"，只是让它在 \"《论语》风格问答\" 这个特定场景下，输出格式更稳定、内容更可预测。这与路线图第三阶段 \"LLM 是一个不稳定但有价值的外部能力源\" 的核心论断完全一致。\u003C\u002Fp>\u003C\u002Fli>\n \u003Cli>\u003Cp>\u003Cstrong>失败模式主要是 AI 系统的设计问题，而非 LLM 本身的能力问题\u003C\u002Fstrong>：上述 4 种失败模式——过拟合、欠拟合、行为漂移、训练崩溃——都可以通过\u003Cstrong>调整数据集、调整超参、调整 prompt template\u003C\u002Fstrong> 等 AI 系统设计手段来缓解，而不是 \"Qwen 2.5-0.5B 本身不够好\"。这与路线图第三阶段 \"哪些是 LLM 本身能力的问题，哪些是 AI 系统的设计问题\" 的提问框架完全对齐。具体到本次实验，\"欠拟合\"的真正原因是\u003Cstrong>数据集只有 10 条\u003C\u002Fstrong>，而非模型能力或 LoRA 配置问题 —— 要缓解，下一步应从\"AI 系统设计\"层（数据采集 + 数据增强）入手，而不是去换更大的模型。\u003C\u002Fp>\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>需要强调的是，这篇笔记所记录的实验是在 \u003Cstrong>i5-9500T（6 核 6 线程、2.2GHz 基频、不支持 AVX-512）+ 16GB 内存 + 0.5B 模型 + 10 条《论语》样本\u003C\u002Fstrong> 这一 \u003Cstrong>消费级 CPU + 极小参数规模 + 极小数据集\u003C\u002Fstrong> 的组合下完成的——与路线图第三阶段 \"建议读者根据自身所拥有的设备条件选择路径\" 中的 \"路径 2（设备条件允许）\" 一致，只是我们在这里走的是路径 2 的 \"低端边缘\"。因此，这篇笔记中所有观察都应该被严格限定在 \"消费级 CPU + 小参数量模型 + 极小数据集\" 这个范畴内，不涉及分布式训练或更大规模的微调实验。\u003C\u002Fp>\n\u003Ch2>参考资料\u003C\u002Fh2>\n\u003Cul>\n \u003Cli>\u003Cp>论文资料\u003C\u002Fp>\n  \u003Cul>\n   \u003Cli>\u003Cem>LoRA: Low-Rank Adaptation of Large Language Models\u003C\u002Fem>（Edward J. Hu 等人，2021）：\u003Ca href=\"https:\u002F\u002Farxiv.org\u002Fabs\u002F2106.09685\" target=\"_blank\" rel=\"noopener noreferrer nofollow\">arXiv:2106.09685\u003C\u002Fa>\u003C\u002Fli>\n   \u003Cli>\u003Cem>QLoRA: Efficient Finetuning of Quantized LLMs\u003C\u002Fem>（Tim Dettmers 等人，2023）：\u003Ca href=\"https:\u002F\u002Farxiv.org\u002Fabs\u002F2305.14314\" target=\"_blank\" rel=\"noopener noreferrer nofollow\">arXiv:2305.14314\u003C\u002Fa>\u003C\u002Fli>\n   \u003Cli>\u003Cem>DoRA: Weight-Decomposed Low-Rank Adaptation\u003C\u002Fem>（Shih-Yang Liu 等人，2024）：\u003Ca href=\"https:\u002F\u002Farxiv.org\u002Fabs\u002F2402.09353\" target=\"_blank\" rel=\"noopener noreferrer nofollow\">arXiv:2402.09353\u003C\u002Fa>\u003C\u002Fli>\n  \u003C\u002Ful>\u003C\u002Fli>\n \u003Cli>\u003Cp>视频资料\u003C\u002Fp>\n  \u003Cul>\n   \u003Cli>什么是 LoRA？大模型微调是怎么回事？：\u003Ca href=\"https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=hZ6fSjPGQWM&amp;t=2s\" target=\"_blank\" rel=\"noopener noreferrer nofollow\">YouTube 链接\u003C\u002Fa> \u002F \u003Ca href=\"https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1PvwYzxE9D\" target=\"_blank\" rel=\"noopener noreferrer nofollow\">Bilibili 链接\u003C\u002Fa>\u003C\u002Fli>\n   \u003Cli>使用 LLaMA-Factory 微调 Qwen3-1.7B 模型：\u003Ca href=\"https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=jmZb90Yen0A\" target=\"_blank\" rel=\"noopener noreferrer nofollow\">YouTube 链接\u003C\u002Fa> \u002F \u003Ca href=\"https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1cE1KBeEVn\" target=\"_blank\" rel=\"noopener noreferrer nofollow\">Bilibili 链接\u003C\u002Fa>\u003C\u002Fli>\n  \u003C\u002Ful>\u003C\u002Fli>\n\u003C\u002Ful>","[!NOTE] 笔记说明 这篇笔记对应的是《[[关于 AI 的学习路线图]]》一文中所规划的第三个学习阶段。其中记录了我尝试在个人开发环境中对一款轻量型的 LLM 进行微调的实验过程，以及个人在该过程中所获得的心得体会。同样的，这些内容也将成为我 AI 系列笔记的一部分，被存储在本人 Github 上的计算机学习笔记库中，并予以长期维护。 在正式开始实验之前，我首先需要与读者建立一些约定，用于确保这篇笔记所记录的实验过程与相关内容能真正发挥它的作用。由于这个实验的目的是通过实际操作过程来观察 LLM 微调的实际作用，并进而观察这项技术在 AI 系统中所扮演的角色，所以我认为实验环境应该是面向普通的个人开发者的。换言之，这篇笔记应该记录的是基于普通笔记本电脑或 Mini PC 这类设备环境所进行的实验，它们通常没有独立显卡，基本配置如图 1 所示。这意味着，我在实验中只能使用 CPU 版本的微调工具环境，针对Qwen 2.5-0.5B这种微小规模的 LLM，进行力所能及的基础微调实践。 图 1 这篇笔记所使用的硬件环境 正因如此，对于我在这篇笔记中的所有观察以及得出的结论，读者应将其严格限定在 ‘消费级 CPU 硬件 + 小参数量模型’的范畴内，不涉及分布式训练或大模型全量微调。这既是这篇笔记的局限所在，也是实验最真实的参考坐标。 [!WARNING] 友情提示 如果不是出于体验流程、理解其工作方式的实验目的，强烈建议读者不要像我一样在一台没有独立显卡的设备上运行 LLM 的微调。因为，在 CPU 上运行微调任务不仅有速度极慢，很容易失败等问题，而且微调的效果也不具备任何现实意义。 知识准备 现在，让我们先来了解一下什么是 LLM 微调（Fine-tuning）。简单来说就是，在经历了我在《[[深度学习的训练与评估]]》这篇笔记中所介绍的、被称为 “预训练” 的处理之后，我们会得到一个各方面都具备一定知识的 LLM。但是，它的知识新旧取决于预训练的截止日期，知识的广度和深度取决于预训练使用的语料，以及在后续有监督微调（Supervised Fine-Tuning，以下简称 SFT）阶段引入的、由其他 LLM 通过知识蒸馏或合成生成的训练数据。所以在通常情况下，人们在将它部署为网络服务之前，都会使用一系列小批量数据做一次再训练，以便将它调整更符合某一特定市场的法律法规、用户习惯，或者更适用于它将要执行的任务类型。换言之，读者可以将 LLM 的预训练与微调的关系理解成：预训练 = 教 LLM 学会人类的语言和世界知识；微调 = 在这个基础上对 LLM 进行针对性培训。 举个例子，假设你有一个已经训练好的通用 LLM，在没有经过微调之前，它可能只能做下面这样的一般性问答： 输入：解释一下什么是 HTTP 输出：HTTP 是超文本传输协议的缩写，用于在互联网上传输数据。 现在，如果我们希望它能在输出回答时做一些代码审查，就需要准备一批像下面这样的训练数据： 输入： 检查下面这段代码…… [Python 代码] 输出： 1. 这里存在…… 2. 原因是…… 3. 建议修改为…… 然后让 LLM 在这些数据上进行再训练，在这轮训练完成之后，它就能实现一些更偏向于代码审查任务的问答了，例如像下面这样。 输入： 检查下面这段 Python 代码： ```Python def load_jsonl(path): \"\"\"从 jsonl 文件加载训练样本。每行一个 JSON 对象。\"\"\" samples = [] with open(path, 'r') as f: for line in f: samples.append(json.loads(line.strip())) return samples ``` 输出： 1. 这里存在：函数没有指定文件编码，同时对每一行的 JSON 解析失败没有任何处理。 2. 原因是：在 Windows 平台上以默认编码打开 UTF-8 文件很容易触发 UnicodeDecodeError；而数据集只要有一行 JSON 格式损坏，整个加载流程就会立刻中断，导致后续训练无法继续。 3. 建议修改为：在 open() 中显式声明 encoding='utf-8'，并对 json.loads 用 try\u002Fexcept 包裹，捕获 json.JSONDecodeError 后跳过坏行。 需要注意的是，正确理解上面所说的 “更倾向” 这个词是非常重要的。在我们继续讨论具体的微调技术之前，先厘清一个常见的误解：微调并不是简单地给 LLM 增加一个可在运行时进行查询的数据库（那是 RAG 的工作方式，我会在《[[Agent 的记忆机制]]》这篇笔记中具体介绍它），它本质上是通过改变参数的方式，使 LLM 在面对特定输入时更容易产生人们希望得到的输出。截止到我写这篇笔记的时间（2026-08-21），市面上常用的微调技术主要有三大类，具体如表 1 所示。 大类 主要思路 代表方法 资源开销 典型适用场景 全参数微调 (FFT) 在下游任务上更新模型的所有参数 — 极高（与模型参数量线性相关） 数据中心 \u002F 多卡集群上的基础模型继续训练 参数高效微调 (PEFT) 冻结原模型，只更新一小部分参数或新增的可学习模块 LoRA \u002F QLoRA \u002F DoRA \u002F AdaLoRA \u002F LoHa \u002F LoKr \u002F Adapter \u002F IA³ 等 低（通常只占原参数量的不到 1%） 单卡 \u002F 个人开发者 \u002F 在已有基座上做领域适配 偏好对齐类 在 SFT 基础上进一步让模型行为对齐人类偏好 RLHF (PPO) \u002F DPO \u002F KTO \u002F ORPO 中到高 对齐对话模型的行为、安全、风格 表 1 LLM 微调技术的主要分类 在上述几类方法之中，PEFT 在过去几年间已经从一种 \"在资源受限时不得不用的妥协方案\"，逐步演变为几乎所有主流 LLM 微调工作的默认起点。而我们在这里将要用到的 LoRA 则是这一众 PEFT 方法之中，被使用得最多、被研究得最透、对生态支持最完善的那一个。下面我们就来重点介绍一下它。 LoRA 简介 LoRA 这个微调方法最早出自一篇名为 LoRA: Low-Rank Adaptation of Large Language Models 的论文。其核心思想可以概括为一句话：LLM 在适配下游任务时，权重矩阵的更新量 \\(\\Delta W\\) 具有较低的本征秩（intrinsic rank），因此无需更新完整的权重矩阵 \\(W\\)，只需要用两个低秩矩阵的乘积来近似 \\(\\Delta W\\) 即可。如果将这句话翻译成更精确的数学语言来描述，那就是：对于 Transformer 中任意一个需要适配的权重矩阵 \\(W\\)（维度为 \\(d \\times k\\)），我们都可以用两个小矩阵 \\(B\\)（维度 \\(d \\times r\\)）与 \\(A\\)（维度 \\(r \\times k\\)）的乘积 \\(BA\\) 来表达 \\(\\Delta W\\)，即 \\(\\Delta W = BA\\)，其中 \\(r\\) 远小于 \\(d\\) 与 \\(k\\)（实践中通常取 4、8、16、32 这样的值），具体公式如下： \\[W' = W + \\Delta W = W + \\frac{\\alpha}{r} B A \\] 在该公式中，\\(W\\) 在训练期间始终保持冻结，\\(A\\) 与 \\(B\\) 为可训练参数，\\(\\alpha\\) 是一个缩放系数（通常与 \\(r\\) 同阶），用于控制 LoRA 更新的整体强度。在推理阶段，可以直接把 \\(BA\\) 合并回 \\(W\\)，得到一个与原始模型结构完全一致的权重矩阵，因此 LoRA 不会给推理带来任何额外的延迟。 当然，在那篇最早提出 LoRA 方法的论文中，作者们将 self-attention 中的 \\(W_q\\)、\\(W_k\\)、\\(W_v\\)、\\(W_o\\) 作为潜在适配目标，并在 Section 7.1 的实证研究中发现同时适应 \\(W_q\\) 与 \\(W_v\\) 在相同参数预算下效果最佳，这也正是 HuggingFace PEFT 库默认只把这两个矩阵设置为target_modules的原因；而在 LLaMA-Factory 等工程框架的默认配置（如本文 YAML 中实际使用的 lora_target: all）中，LoRA 适配则会进一步扩展到全部线性层，以增强在中小规模数据集上的适配稳定性。不过在后续的工程实践中，LoRA 已经被普遍推广到了前馈网络中的矩阵以及 embedding、LM head 等位置中，研究者们也沿着 \"低秩分解\" 这条主线发展出了多条改进路径： QLoRA（由 Dettmers 等人于 2023 年提出，相关论文是 arXiv:2305.14314）：先把用于充当基座的 LLM 量化到 4-bit NormalFloat（NF4），再在其上挂载 LoRA，从而将原本需要多卡高端 GPU 才能完成的、65B 规模的 LLM 微调，压缩到单卡 48GB GPU（如 RTX A6000 48GB、A100 40GB）即可完成的规模； DoRA（由 Liu 等人于 2024 年提出，相关论文是 arXiv:2402.09353）：把权重矩阵拆成幅值（magnitude）与方向（direction）两部分，其中，方向的部分沿用 LoRA 处理，幅值的部分则用一个可训练的缩放向量来表达，在许多任务上获得了比 LoRA 更好的效果； AdaLoRA：自适应地为不同层、不同模块分配不同的秩 \\(r\\)； LoHa \u002F LoKr：分别用低秩 Hadamard 积与低秩 Kronecker 积来表示 \\(\\Delta W\\)，进一步丰富了 \"低秩适配\" 的表达形式。 这些方法在思路上都与 LoRA 同源，因此读者只要把 LoRA 的原理弄清楚了，再去理解后续的改进工作就会顺畅很多。 为什么选择 LoRA 现在，让我们回到接下来要进行的实验设定上了。根据我们在笔记开篇时的硬件约定，实验使用的是普通笔记本电脑或 Mini PC 这类基本没有独立显卡的设备。这就意味着，设备的显存与内存容量是制约我们能跑多大参数的 LLM、能训练多久的主要瓶颈。在这种情况下，LoRA 在资源开销上所具备的以下这几项优势，让它几乎成为了我们进行这个实验的唯一选择。 可训练参数大幅减少：LoRA 方法需要训练的参数量通常都只有 LLM 全部参数的 1% 以下，这就直接降低了 Adam 等优化器所需要维护的状态（动量、方差等）的存储开销； 激活显存与梯度显存显著下降：在 LoRA 方法中，被冻结的 \\(W\\) 在反向传播中不需要保存梯度，反向传播也只在小矩阵 \\(A\\)、\\(B\\) 上进行； 训练产物小巧且易于管理：一次训练只产出一个体积通常只有几十到几百 MB 的 adapter 文件，便于在不同数据集、不同超参配置下进行多轮实验，并按需切换、叠加； 生态成熟：Hugging Face PEFT、LLaMA-Factory 等主流的 LLM 微调框架都对 LoRA 方法提供了完善的支持，从命令行工具到 WebUI 都有现成的封装。 具体到本实验的目标对象Qwen 2.5-0.5B，它的参数量虽然不大，但即使是 0.5B 这种轻量级模型，全量微调在 CPU 环境下仍然会占用相当可观的内存与计算资源，训练时长也往往会拉长到令人难以接受的水平；而改用 LoRA 之后，单次训练的内存峰值与时长都能降到本实验硬件可以承受的范围内，训练产物也便于我们在后续阶段对它进行二次评估与对比。 最后，还需要说明的是，我在这次实验中之所以选择 LoRA 而不是 QLoRA、DoRA 等更新的改进方法，主要是因为 LoRA 是整个 LoRA 家族中最为经典、对 CPU 环境最友好、社区资料最丰富的那一种，对一名初学者来说，它是理解 \"PEFT\" 这条技术路线最合适的起点。 实验过程记录 在完成了上述知识准备之后，现在让我们正式开始 LLM 的微调实验吧，其具体步骤如下。 安装 uv 并创建一个基于 Python 3.12 的虚拟环境。关于这部分的操作方法，我已经在《[[编程环境配置|Python 学习笔记：编程环境配置]]》（博客园链接）这篇笔记中做过详细介绍，这里就不再赘述了。总而言之，在完成这些操作之后，我们会得到如图 2 所示的结果。 图 2 创建基于 Python 3.12 的虚拟环境 基于《论语》这本书的文本准备一个极小规模的微调数据集，这可以使用 OpenCode 这样的 Agent 工具帮我们自动生成，提示词为：“我需要基于 LLaMA-Factory 来进行一次 LLM 微调实验，帮我生成一份基于《论语》的微调数据集，数据集格式为 JSON Lines，每条数据包含一个问题和对应的答案，数据集大小为 10 条。” 具体如图 3 所示。 图 3 OpenCode 自动生成微调数据集 安装 LLaMA-Factory 及其 CPU 版的依赖，这需要我们打开命令行终端，并依次执行以下命令： # 安装 LLaMA-Factory pip install llamafactory # 安装 CPU 版 PyTorch pip install torch torchvision torchaudio --index-url https:\u002F\u002Fdownload.pytorch.org\u002Fwhl\u002Fcpu 如果一切顺利，待上述操作执行完成之后，我们在命令行终端中输入llamafactory-cli help，应该会看到如图 4 所示的结果。 图 4 LLaMA Factory 帮助信息 配置并运行 CPU 版的 LoRA 微调，这需要我们执行llamafactory-cli webui命令，打开 LLaMA-Factory 的 Web 操作界面，并按照图 5 所示的步骤，选择我们接下来要使用的 LLM 权重文件与微调数据集。 图 5 LLaMA-Factory 的 Web 操作界面 默认情况下，LLaMA-Factory 的最新版本会自行从 Hugging Face 或 ModelScope 平台上获取到我们在上述界面中指定的 LLM 权重文件，但如果是使用旧版，且无法连接 Hugging Face 的用户，那就得亲自去 ModelScope 网站上搜索到Qwen 2.5-0.5B，并按照图 6 中所示的步骤下载权重文件。 图 6 找到 Qwen 2.5-0.5B 权重文件 其具体操作过程如图 7 所示。读者可以注意到，待Qwen 2.5-0.5B的权重文件下载完成之后，ModelScope 的下载器会自动告诉我们文件的存储位置，如果需要的话，我们也可以将它另存到一个比较方便管理的位置上。然后再将该位置填写到图 5 所示界面的 “模型路径” 字段中，这样，我们就可以在后续的微调过程中使用本地的权重文件了。 图 7 下载 Qwen 2.5-0.5B 权重文件 按照图 3 中 OpenCode 给出的提示，将它替我们准备好的数据集注册到 LLaMA-Factory 中，并将该数据集命名为lunyu_sample，然后在刷新 Web 界面之后，我们就可以顺利将该数据集加载到 LLaMA-Factory 中了，如图 8 所示。 图 8 注册并加载微调数据集 由于我们执行的是 CPU 版的 LoRA 微调，因此需要按照表 2 所示的配置，在 LLaMA Factory 的 Web 界面中设置训练参数。 参数 本实验使用值 相关说明 finetuning_type lora 全参数微调要更新 LLM 里几亿个参数，CPU 上既吃内存又慢到没法等；LoRA 只额外训练几个小矩阵、其余全部冻结，是 CPU 上唯一现实的选择 model_name_or_path Qwen\u002FQwen2.5-0.5B 没独立显卡时，显存就是内存。LLM 越大，加载占用的内存和每一步的计算量就越大；0.5B 是这个条件下既能跑起来、又有基本智能的最小模型之一 bf16 \u002F fp16 false \u002F false bf16\u002Ffp16 是给 GPU 的半精度加速玩法：在 CPU 上要么直接不支持（报错 \"expected scalar type Float but found Half\"），要么精度损失让训练结果变差；保持两者均为 false 即走 fp32，最稳，慢一点但不会翻车 cutoff_len 256 序列越长，单步计算量越大，CPU 就越慢；Qwen2.5 虽支持 3 万 token 长上下文，但《论语》问答样本最长也不超过 200 token，主动截到 256 能省大量时间，效果几乎不受影响 batch_size 2 GPU 一次能并行处理几十条样本，CPU 上只能串行；本实验实测将 batch_size 设为 2 时，6 核 CPU 的负载比较均衡，单步耗时比 1 反而更短 gradient_accumulation_steps 2 batch 太小会让梯度噪声大、训练不稳；本实验的有效 batch size 为 2×2=4（小数据集下 GA 设大反而拖慢训练且不改善 loss 曲线） learning_rate 1e-4 LoRA 只更新少量参数，本质就是在已有的 LLM 上轻轻推一把，所以可以用比全参数微调更大的学习率；本实验取推荐区间下界 1e-4，配合极小数据集更稳妥 表 2 CPU 版 LoRA 微调的参数配置 在完成上述参数设置之后，我们就可以点击页面中的 “开始” 按钮来启动 LoRA 微调了，如图 9 所示。 图 9 开始 LoRA 微调 补充：等价的命令行方式。上面的 WebUI 操作也可以通过一份 YAML + 一行命令来等价复现，便于脚本化或在 SSH 远程机上运行： llamafactory-cli train train_lunyu_sample_fast.yaml 其中 train_lunyu_sample_fast.yaml 中的主要参数设定与表 2 一一对应，具体如下： model_name_or_path: Qwen\u002FQwen2.5-0.5B # 模型名称 dataset: lunyu_sample # 数据集名称 dataset_dir: data # 数据集存储路径 template: qwen # 模板名称 finetuning_type: lora # 微调类型 lora_rank: 8 # LoRA 矩阵的秩 lora_alpha: 16 # LoRA 矩阵的缩放因子 lora_dropout: 0 # LoRA 矩阵的丢弃率 lora_target: all # LoRA 矩阵的目标层 stage: sft # 微调阶段 do_train: true # 是否进行训练 output_dir: saves\\Qwen2.5-0.5B\\lora\\train_lunyu_sample_fast # 输出目录 overwrite_output_dir: true # 是否覆盖输出目录 cutoff_len: 256 # 截断长度 per_device_train_batch_size: 2 # 每个设备的训练批次大小 gradient_accumulation_steps: 2 # 梯度累积步数 learning_rate: 0.0001 # 学习率 num_train_epochs: 3.0 # 训练轮数 lr_scheduler_type: cosine # 学习率调度器类型 warmup_steps: 0 # 预热步数 max_grad_norm: 1.0 # 最大梯度范数 logging_steps: 1 # 日志记录步数 save_steps: 6 # 保存步数 plot_loss: true # 是否绘制损失曲线 preprocessing_num_workers: 6 # 预处理工作线程数 dataloader_num_workers: 4 # 数据加载工作线程数 flash_attn: auto # 是否使用 Flash Attention bf16: false # 是否使用 bf16 fp16: false # 是否使用 fp16 gradient_checkpointing: false # 是否使用梯度检查点 optim: adamw_torch # 优化器类型 packing: false # 是否使用打包 enable_thinking: false # 是否启用思考 report_to: none # 报告类型 trust_remote_code: true # 是否信任远程代码 seed: 42 # 随机种子 这份 YAML 与 WebUI 表单的最大差异在于：它可被纳入到 Git 版本控制系统中进行管理，这是当前任务做成 \"可复现实验\" 的关键一步。后续如果我们的设备条件允许，需要换成Qwen2.5-1.5B或切换到Qwen3-1.7B，只需要修改model_name_or_path和cutoff_len，其他参数都不用动，就能复现同样的实验结果。 待训练开始之后，我们就能在 LLaMA-Factory 的 Web 界面的底部看到训练过程的实时输出框了，并且在训练持续一段时间之后，训练进度条也会在实时输出框的上方出现，如图 10 所示。由于我们使用的纯 CPU 的微调方式，这个过程会非常慢，基本上都要持续 2-5 个小时不等，且失败几率很高。 图 10 LoRA 微调训练进度 一旦训练完成，我们就会在 LLaMA-Factory 的 Web 界面中看到上面的训练进度条走到了 100%。同时，”中断“ 按钮的下方会出现一张完整的 loss 曲线图，如图 11 所示。 图 11 ”中断“ 按钮下方出现的 loss 曲线图 至于训练的具体成果究竟如何，我们可以从以下几个关键信号来判断： loss 曲线持续下降并最终趋于平稳：train_loss 在前几个 epoch 出现明显下降（起步通常在 1.5~3.0 区间，收敛后稳定在 0.1~0.5）；eval_loss 与 train_loss 趋势一致（若 train_loss 下降而 eval_loss 上升，则视为过拟合）。一个粗略的经验阈值：train_loss > 1.0 多为欠拟合，\u003C 0.3 可视为基本收敛。 grad_norm 稳定在 0.1-10 之间：如果突然飙升到 100+ 或者出现 NaN，说明训练崩溃，需要回退学习率或检查数据。 没有 OOM \u002F RuntimeError：CPU 训练里常见的报错包括 \"DataLoader worker (pid X) is killed by signal: Out of memory\"（需减小batch_size）以及 \"expected scalar type Float but found Half\"（即 PyTorch CPU 版本与 bf16 不兼容，需切回 fp32）。 总时长在 2-5 小时之间：如果超过 5 小时，说明训练过程可能卡住；如果低于 2 小时，说明训练过程可能过快，需要检查是否有数据问题。 当以上信号都出现时，就基本可以确认 LoRA 微调已经成功完成。下一步，我们就可以进入实验结果的分析环节了。 实验结果与分析 完成上述 10 个步骤后，整个实验就基本跑完了。需要说明的是，根据《[[关于 AI 的学习路线图]]》第三阶段的规划，我进行这个实验的核心任务是要通过 LLM 在微调前后的行为差异，来观察 \"当训练数据和优化目标发生变化后，LLM 的行为如何变化\" —— 也就是回答我在路线图第三阶段中提出的两个核心问题： LLM 在 AI 系统中是核心决策单元，还是能力增强模块？ 哪些是 LLM 本身能力的问题，哪些是 AI 系统的设计问题？ 接下来，我会先把实测的训练过程与产物数据展开，再用这些数据回答路线图的这两个核心问题。 实际观察到的结果 首先，让我们先把之前在步骤 10 中所列的实验成功信号及其生成的 loss 曲线一并展开，然后再结合训练输出目录里的真实产物，逐项用数据来说话。 训练日志维度：4 个成功信号逐条核对 信号 阈值 实测 结论 1. loss 曲线下降并趋于平稳 下降 2.07 → 1.73（波动下降，幅度约 16%） 部分通过（有波动） 2. grad_norm 稳定在 0.1-10 区间 区间 1.87 – 3.44 通过 3. 无 OOM \u002F RuntimeError 无报错 无 通过 4. 总时长 2-5 小时 区间 16,220 秒 ≈ 4 小时 30 分 通过 4 个信号里 3 个完全通过、1 个部分通过。这说明这次实验在工程层面确实跑通了，只是 loss 曲线未完全平稳收敛（这是数据集太小所致，我稍后会做详细说明）。 loss 曲线的 9 个数据点 step loss grad_norm learning_rate epoch 备注 1 2.0652 3.028 1.00e-4 0.40 起步 2 1.9221 2.160 9.70e-5 0.80 3 1.7187 3.435 8.83e-5 1.00 第一轮结束 4 1.7484 1.966 7.50e-5 1.40 5 1.9943 2.571 5.87e-5 1.80 反弹 6 1.4943 2.298 4.13e-5 2.00 谷值 7 1.5658 2.037 2.50e-5 2.40 8 1.7383 1.872 1.17e-5 2.80 9 1.7325 2.677 3.02e-6 3.00 最终 可以从曲线上看到几个关键特征： 起步 loss = 2.07：在我之前在步骤 10 中给出的经验起点 1.5-3.0 区间内，符合预期。 波动下降：loss 没有单调下降。step 4→5 反弹到 1.99、step 7→8 反弹到 1.74、step 8→9 反弹到 1.73。这种波动在 9 个 step 的小样本训练里是正常的，主要由 cosine schedule 的 learning_rate 衰减曲线驱动 —— 当 learning_rate 衰减到接近 0 的尾段，loss 反而会反弹一小段。 最终 loss = 1.73：按经验阈值（> 1.0 多为欠拟合），严格来说这次训练仍属欠拟合——但这是数据集太小（10 条）而非方法问题，下面\"失败模式\"小节会再展开。 grad_norm 全程 1.87-3.44：远低于 100+ 的崩溃阈值，全程在 0.1-10 的健康区间内，证明训练过程稳定。 训练产物：output_dir 里到底有什么 saves\u002FQwen2.5-0.5B\u002Flora\u002Ftrain_2026-09-02-10-24-13\u002F ├── adapter_model.safetensors (17.6 MB) # LoRA 适配器权重（核心产物） ├── adapter_config.json (1.1 KB) # LoRA 适配器配置 ├── chat_template.jinja (2.4 KB) # Qwen2.5 对话模板 ├── tokenizer.json (10.9 MB) # tokenizer 配置（推理用） ├── tokenizer_config.json (0.7 KB) # tokenizer 元信息 ├── all_results.json (0.2 KB) # 最终汇总指标 ├── train_results.json (0.2 KB) # 训练结果指标 ├── trainer_state.json (3.6 KB) # 训练状态 + 9 条 loss 原始数据 ├── trainer_log.jsonl (2.2 KB) # 逐 step 日志 ├── training_loss.png (34.5 KB) # loss 曲线图（对应图 11） ├── training_args.yaml (0.8 KB) # 训练参数快照 ├── training_args.bin (5.8 KB) # 训练参数二进制 ├── README.md (1.6 KB) # PEFT 自动生成的 model card ├── llamaboard_config.yaml (2.4 KB) # LLaMA-Factory WebUI 配置快照 ├── running_log.txt (15.1 KB) # 完整运行日志 └── checkpoint-9\u002F # 第 9 步中间检查点（含 optimizer.pt \u002F rng_state.pth \u002F scheduler.pt） 最关键的产物是 adapter_model.safetensors —— 17.6 MB 的 LoRA 适配器权重，可以独立于基座模型加载，与原始的 Qwen 2.5-0.5B 模型叠加使用实现微调效果。 数据来源说明：这里的 9 个 loss 数据点、grad_norm 范围 1.87-3.44、最终 loss 1.7325、训练时长 16,220 秒（约 4 小时 30 分 20 秒）、num_input_tokens_seen 5,488 等数字均直接取自该目录下的 all_results.json（最终汇总指标）、train_results.json、trainer_state.json（loss 曲线原始数据）、trainer_log.jsonl（逐 step 日志）等文件。这 4 个文件均已随本笔记一并入库到 examples\u002Fllama_factory\u002Fsaves\u002FQwen2.5-0.5B\u002Flora\u002Ftrain_2026-09-02-10-24-13\u002F，如需对实验数据做进一步分析或绘制其他曲线，可直接读取这些原始文件，无需重新跑训练。 adapter 的实际 target_modules adapter_config.json 里的 target_modules 字段展开后是： [\"q_proj\", \"k_proj\", \"v_proj\", \"o_proj\", \"gate_proj\", \"up_proj\", \"down_proj\"] 这正是 lora_target: all 在 LLaMA-Factory WebUI 下的实际展开 —— Transformer 解码器层的全部 7 个线性层（注意力的 Q\u002FK\u002FV\u002FO + FFN 的 gate\u002Fup\u002Fdown）。这也印证了前文关于\"LLaMA-Factory 默认会扩展到全部线性层\"的描述。 行为维度（基于产物的推断，非实测对比） 需要注意，这次实验没有做 \"微调前 vs 微调后\" 的对比测试。前文路线图里提到的\"通过 LLM 在微调前后的行为差异来观察模型行为如何变化\"，严格来说这次实验只完成了 \"微调\" 半边。至于另一半（行为对比测试）按开头 WARNING 的原则属于 \"不具备任何现实意义\" 的活动，我刻意没做。因此接下来对\"行为变化\"的所有观察都是基于训练产物与 LLaMA-Factory 经验的推导，而非实测对比： LLM 在《论语》问答任务上的输出格式应该比微调前更稳定（数据集全是统一 prompt 模板的问答对）； LLM 对\"论语\"主题知识的吸收几乎不会有显著提升（10 条样本本身承载的知识量极低）； 想要验证行为变化，最稳妥的方式是用 adapter_model.safetensors 加载后跑一批测试 prompt，对比同 prompt 下原始模型与微调模型的输出 —— 但这属于\"推理与评估\"环节，本笔记暂不展开。 失败模式与边界 呼应路线图第三阶段 \"当 LLM 输出不可靠时，AI 系统该如何处理能力的退化\" 这一关键问题，这次实验在以下几种情况下应当视为 \"未达到预期\" 或 \"需要重新设计\"： 过拟合：train_loss 持续下降但 eval_loss 反而上升（步骤 10 第 1 条）。这说明微调输出的 adapter 只 \"记住\" 了训练集而没有学到《论语》的语言模式。 欠拟合：train_loss 始终在 1.5-2.0 之间下不来。这说明可能学习率过低、rank 过小、或者数据集质量有问题。 行为漂移：微调后 LLM 对一般性问答的回答质量明显下降。这说明数据集分布过于狭窄（全部是《论语》），导致模型丧失了原有的 \"通识\" 能力。 训练崩溃：loss 出现 NaN 或 grad_norm 飙升到 100+。这说明我们需要回退学习率或检查数据标注。 对照本次实验：上面四种失败模式本次仅触及\"欠拟合\"一项的边缘： 过拟合：本实验没有 eval_loss（数据集只有 10 条，根本无法划分 eval 集），过拟合判断无从谈起。这是数据集规模决定的结构性约束。 欠拟合：最终 loss 1.73 > 1.0，从阈值角度属\"欠拟合\"。但 10 条样本 + 3 epoch 本身就是\"明知会欠拟合的实验设置\"，可接受 —— 如要缓解，应扩展数据集（而非调整学习率或 rank）。 行为漂移：未做对比测试，未观察到。 训练崩溃：loss 无 NaN、grad_norm 全程 1.87-3.44、未触发任何崩溃。 在 AI 系统中的角色讨论 基于上述结果，我们可以从两个层面回答路线图第三阶段的核心问题： LLM 在 AI 系统中更接近 \"能力增强模块\" 而非 \"核心决策单元\"：我在实验中所采用的 \"在极小数据集上做轻量级 LoRA 微调\" 这一过程，本质上是为一个通才 LLM 增加了一个针对特定任务的小型插件。从产物角度看，本实验的产出是一个仅占基座模型 ~1.7% 体积（17.6 MB vs ~1 GB 0.5B 模型）的 LoRA 适配器。这个量级的产物能做的事情只有\"小幅度改变基座模型在特定任务上的输出倾向\"，而不是\"重新训练模型\"。很显然，这样做没有让Qwen 2.5-0.5B这个 LLM 变成 \"国学大师\"，只是让它在 \"《论语》风格问答\" 这个特定场景下，输出格式更稳定、内容更可预测。这与路线图第三阶段 \"LLM 是一个不稳定但有价值的外部能力源\" 的核心论断完全一致。 失败模式主要是 AI 系统的设计问题，而非 LLM 本身的能力问题：上述 4 种失败模式——过拟合、欠拟合、行为漂移、训练崩溃——都可以通过调整数据集、调整超参、调整 prompt template 等 AI 系统设计手段来缓解，而不是 \"Qwen 2.5-0.5B 本身不够好\"。这与路线图第三阶段 \"哪些是 LLM 本身能力的问题，哪些是 AI 系统的设计问题\" 的提问框架完全对齐。具体到本次实验，\"欠拟合\"的真正原因是数据集只有 10 条，而非模型能力或 LoRA 配置问题 —— 要缓解，下一步应从\"AI 系统设计\"层（数据采集 + 数据增强）入手，而不是去换更大的模型。 需要强调的是，这篇笔记所记录的实验是在 i5-9500T（6 核 6 线程、2.2GHz 基频、不支持 AVX-512）+ 16GB 内存 + 0.5B 模型 + 10 条《论语》样本 这一 消费级 CPU + 极小参数规模 + 极小数据集 的组合下完成的——与路线图第三阶段 \"建议读者根据自身所拥有的设备条件选择路径\" 中的 \"路径 2（设备条件允许）\" 一致，只是我们在这里走的是路径 2 的 \"低端边缘\"。因此，这篇笔记中所有观察都应该被严格限定在 \"消费级 CPU + 小参数量模型 + 极小数据集\" 这个范畴内，不涉及分布式训练或更大规模的微调实验。 参考资料 论文资料 LoRA: Low-Rank Adaptation of Large Language Models（Edward J. Hu 等人，2021）：arXiv:2106.09685 QLoRA: Efficient Finetuning of Quantized LLMs（Tim Dettmers 等人，2023）：arXiv:2305.14314 DoRA: Weight-Decomposed Low-Rank Adaptation（Shih-Yang Liu 等人，2024）：arXiv:2402.09353 视频资料 什么是 LoRA？大模型微调是怎么回事？：YouTube 链接 \u002F Bilibili 链接 使用 LLaMA-Factory 微调 Qwen3-1.7B 模型：YouTube 链接 \u002F Bilibili 链接",13577,{"id":6,"kind":7,"title":11,"summary":13,"image":14,"href":16,"meta":37,"badge":10,"author":12,"stats":-1,"accent":38,"coverRatio":39,"tags":40},"2026 · 综合技术","#2563eb","16 \u002F 10",[19],{"targetType":8,"targetId":9,"likedByMe":42,"likeCount":43,"commentCount":43,"contentLikeCount":43,"contentCommentCount":43,"sourceLikeCount":43,"sourceCommentCount":43},false,0,[45,52,58,65,72,78,85,92],{"id":46,"kind":7,"title":47,"summary":48,"image":49,"href":50,"meta":37,"badge":10,"author":12,"stats":-1,"accent":38,"coverRatio":39,"tags":51},"NEWS_ARTICLE:843","介绍一下常用的Token鉴权方案","本文梳理 Session‑Cookie、JWT、OAuth2.0、SSO 主流 Token 鉴权方案，对比各方案适用场景。重点讲解生产级 JWT 双 Token 架构，给出 RS256 非对称加密、Redis 黑名单、网关统一鉴权等 Java 实战代码。剖析 JWT 注销、并发刷新、令牌泄露等落地痛","https:\u002F\u002Fimg2024.cnblogs.com\u002Fblog\u002F739056\u002F202609\u002F739056-20260902114155245-1924311389.png","\u002Fnews\u002F843",[19],{"id":53,"kind":7,"title":54,"summary":55,"image":15,"href":56,"meta":37,"badge":10,"author":12,"stats":-1,"accent":38,"coverRatio":39,"tags":57},"NEWS_ARTICLE:854","分治：序列分治（CDQ）与点分治","分治：序列分治（CDQ）与点分治 一、分治思想概述 分治（Divide and Conquer）是算法设计中最核心的思想之一。它的基本策略是： 分（Divide）：将原问题划分为规模更小的子问题。 治（Conquer）：递归地求解子问题（若子问题足够小则直接求解）。 合（Combine）：将子问题的","\u002Fnews\u002F854",[19],{"id":59,"kind":7,"title":60,"summary":61,"image":62,"href":63,"meta":37,"badge":10,"author":12,"stats":-1,"accent":38,"coverRatio":39,"tags":64},"NEWS_ARTICLE:863","弱模型不能裸奔：Agent Harness 凭什么真实有效","Harness 不是给弱模型贴的创可贴。它是把工程纪律——验证、门禁、不变量、路由——变成架构里一等公民的方式。模型每半年换一代，今天省钱的 Flash 明天可能就过时了，但那套'默认怀疑、机器校验、按决策密度调度'的流程会留下来，并且越跑越值钱。\n弱模型不能裸奔。给它穿上 harness，便宜才真","https:\u002F\u002Fimg2024.cnblogs.com\u002Fblog\u002F510\u002F202608\u002F510-20260830181633785-1171146572.jpg","\u002Fnews\u002F863",[19],{"id":66,"kind":7,"title":67,"summary":68,"image":69,"href":70,"meta":37,"badge":10,"author":12,"stats":-1,"accent":38,"coverRatio":39,"tags":71},"NEWS_ARTICLE:865","焕新鸿蒙应用权限管理方案，应用授权体验再升级","作为用户或应用开发者，或许经历过类似的体验场景：使用应用的过程中，触发应用某些功能会需要访问你的位置、麦克风、相机等常用权限，若为了保护隐私拒绝授权后，想要使用功能时，再次打开却找不到设置入口；或开启流程繁琐，需要经过频繁跳转和设置。这一问题不仅影响用户体验，还可能造成应用功能不可用、用户流失，也制","https:\u002F\u002Fimg2024.cnblogs.com\u002Fblog\u002F2396482\u002F202609\u002F2396482-20260901170248450-744043562.png","\u002Fnews\u002F865",[19],{"id":73,"kind":7,"title":74,"summary":75,"image":15,"href":76,"meta":37,"badge":10,"author":12,"stats":-1,"accent":38,"coverRatio":39,"tags":77},"NEWS_ARTICLE:875","别急着翻译 SKILL.md：我做了一个专门拆解 Skill 设计的工具","别急着翻译 SKILL.md：我做了一个专门拆解 Skill 设计的工具 第一次打开一份复杂的 SKILL.md，很多人的反应都是从头往下读。 每句话似乎都认识，连在一起却不一定明白：为什么这里用了 MUST？为什么执行前要读取这些文件？状态由谁维护？AI 做到什么程度才算完成？如果两条指令发生冲突","\u002Fnews\u002F875",[19],{"id":79,"kind":7,"title":80,"summary":81,"image":82,"href":83,"meta":37,"badge":10,"author":12,"stats":-1,"accent":38,"coverRatio":39,"tags":84},"NEWS_ARTICLE:879","高并发下的抢红包设计：微信红包背后的算法与温情","本文拆解微信拼手气红包高并发实现，详解核心**二倍均值算法**，解决红包分配公平性问题。架构上依靠 Redis+Lua 脚本实现原子抢红包，规避超发与重复抢夺；结合 MQ 异步落库、热点 key 拆分、多层限流、定时对账兜底，支撑百万 QPS。给出 Java、Lua 核心源码，梳理超发、缓存一致性、","https:\u002F\u002Fimg2024.cnblogs.com\u002Fblog\u002F739056\u002F202609\u002F739056-20260901095345589-1940958188.png","\u002Fnews\u002F879",[19],{"id":86,"kind":7,"title":87,"summary":88,"image":89,"href":90,"meta":37,"badge":10,"author":12,"stats":-1,"accent":38,"coverRatio":39,"tags":91},"NEWS_ARTICLE:896","如何设计对外API接口","详解生产级对外 API 完整设计方案，围绕易用、安全、健壮黄金三角，覆盖 RESTful 规范、统一响应、签名验签防重放、AOP 注解实现分布式幂等、Redis 令牌桶限流、版本兼容、监控文档。附带可直接复用 Java 核心代码，梳理面试高频技术难点，搭配模拟面试场景，帮你避开线上坑点，快速掌握开放","https:\u002F\u002Fimg2024.cnblogs.com\u002Fblog\u002F739056\u002F202608\u002F739056-20260831134132446-55355388.png","\u002Fnews\u002F896",[19],{"id":93,"kind":7,"title":94,"summary":95,"image":15,"href":96,"meta":37,"badge":10,"author":12,"stats":-1,"accent":38,"coverRatio":39,"tags":97},"NEWS_ARTICLE:906","上周热点回顾（8.24-8.30）","热点随笔： &#183; 从 PostgreSQL 到 Kubernetes：开源的护城河，从来不写在代码里 (张善友) &#183; 代码都能让AI写了，我还学个屁？ (佛祖让我来巡山) &#183; Vibe Coding 月提交量 29 亿次之后：GitHub 的危机、Azure 迁移，以及","\u002Fnews\u002F906",[19]]