ai-agents-from-zero
🚀 2026 最系统的 AI Agent 速成指南|智能体实战教程 · 完整学习路径 + 实战项目 + 面试题库 · 对标大模型应用开发工程师岗位 · 覆盖LangChain / LangGraph / Coze / Dify / MC
🚀 全网最系统的 AI 智能体实战速成指南(从零到企业级落地)
ai-agents-from-zero
2026 持续更新中 · 目标打造「地表最强」AI Agent 教程 —— 系统教程 + 可跑源码 + 面试题库 + 企业级实战项目 + 长期技术栈更新,全面对齐「AI 智能体 / 大模型应用开发工程师」培训课表与招聘 JD的一条龙学习路线
📢 更新说明:AI 不停,更新不止。本仓库将伴随 AI 大模型技术栈持续进化,坚持 开源、系统化、长期更新。模型、框架、Agent、实战项目,都会随着生态变化持续完善和升级。它不只是帮你入门,而是陪你一路成长,从零基础到能真正落地。
目前 概念篇 已全部更新完毕,两个完整实战项目也已更新完毕:NL2SQL + LangGraph 实战项目 电商问数(源码仓库)已于 5 月 3 日完成。DeepAgents 多智能体实战项目 深度研搜(源码仓库)已于 5 月 17 日完成。你可以点击更新日志,了解最新仓库动态。
市面上 AI 大模型应用内容很多,但绝大多数是碎片化帖子、收费训练营等;本仓库就是让你不用先花大几千甚至上万,也能系统进入大模型应用开发。若对你有帮助,欢迎 Star ⭐~
🤝 项目赞助
高性价比 Agent Plan 优选 |
感谢 优云智算 赞助本项目!优云智算是 UCloud 旗下 AI 云平台。主打包月、按次的高性价比国模 Agent Plan 套餐,低至 49 元/月起;同时提供官转稳定海外模型,支持接入 Claude Code、Codex 及 API 调用,面向企业提供高并发、7\*24 技术支持与自助开票。 🎁 通过 此链接 注册的用户,可得免费 5 元平台体验金。 |
✨ 教程亮点
- 🌱 全网首个系统开源的 AI 智能体教程:这是一套长期维护的 AI 大模型应用开发路线图。市面上不缺零散帖子,也不缺收费训练营,但真正系统、持续更新,并且覆盖 教程 + 源码 + 实战项目 + 面试题库 的 AI 大模型应用教程极少。本仓库要做的就是把这条路线公开、做深、做完整,陪你从入门、进阶、项目实战一路成长。
- 🧭 一条线拉通大模型应用全栈:从大模型与提示词,到低代码(Coze/Dify)与代码框架(LangChain/LangGraph),再到企业级 RAG/Agent、微调与工程规范——按知识体系统一编排,完整链路闭环,适合系统吃透而不是碎片化收藏。
- 🐍 聚焦 Python 生态,直击 Agent 工程:很多课程会围绕 Spring AI、langchain4j 展开,更偏 Java 技术栈。本教程主线聚焦 Python + LangChain + LangGraph,直接对齐当下 AI Agent / 大模型应用开发最常用的工程路线。
- 📘 零基础能进,工程师也能深挖:整套教程按由浅入深的方式编排,先把大模型、Agent、RAG、MCP、Tool Calling 这些核心概念讲透,再进入源码、框架、部署和项目设计。少讲玄学黑话,多讲“为什么这么设计、代码怎么跑、项目怎么落地”。
- 💼 企业级实战,对标「能干活」:以电商问数、深度研搜、掌柜智库、电商小二等为主线,串起意图解析、多源知识、转人工、复盘与监控;把 多路召回、评测、观测、成本、护栏 放进真实语境里练。写简历、讲项目有抓手、不空泛。
- ✅ 每个案例都以“能跑起来”为标准:不只是讲概念或贴伪代码,而是尽量提供可运行案例、源码、环境说明和常见问题排查。案例内容均按人工实际跑通的思路整理,帮助你少踩坑、尽快上手。
- 📚 教程源码面试,三位一体能跑通:不止「看完」,还能「跑通」更能「答出来」——可运行案例与源码、提示词模板与部署思路,拒绝「只讲概念」。面试题库按培训班与 JD 常见能力域拆题,其中有相当一部分题目整理自大厂真实面试题、公开面经与高频追问场景,适合转岗/应届集中复盘。
- 🚀 面向 2026,持续进化且硬核:技术栈与高频问法会随生态迭代,对齐「AI 应用开发工程师」万元级培训课表维度;不只告诉你“学什么”,还告诉你“怎么做项目、怎么讲项目、怎么答面试”。若有用,Star 即打赏 ⭐。
🛠 技术栈概览
| 类别 | 技术/平台 | 说明 |
|---|---|---|
| 大模型与基础 | LLM、Transformer、MoE、自注意力 | LLaMA/Qwen/GPT、多模态、预训练/微调/推理 |
| 提示与编排 | 提示词工程、Tool Calling、Skills | 多轮对话、消息模板、结构化输出、工具调用、技能化能力沉淀 |
| 低代码平台 | Coze(扣子)、Dify | 工作流、Agent、知识库、插件、Python 调用与本地化部署 |
| 开发框架 | LangChain、LangGraph、DeepAgents | Model I/O、Runnable / LCEL、Memory、Tools、Agents、图式工作流、多智能体 |
| 协议与通信 | MCP(Model Context Protocol)、A2A | Function Calling、服务解耦、外部工具接入、跨 Agent 协作 |
| RAG 与检索 | 向量数据库、稀疏检索、混合检索、BGE-Rerank | 多路召回、重排序、知识图谱、RAGAS 评估、高级 RAG 优化 |
| 文档与多模态 | MinerU、OCR | 图文混排 PDF 解析、设备手册与售后指南 |
| 部署与运维 | Docker、Ollama、Xinference、vLLM | 腾讯云/阿里云、AutoDL、Coze 本地部署 |
| 微调与训练 | PEFT、LoRA、QLoRA、DeepSpeed、Llama-Factory | Alpaca/ShareGPT 数据格式、Safetensors、关键词评估 |
| 编程与工具 | Python;Codex、Cursor | 主语言为 Python;覆盖 AI 编程工具、Agent Skills、多模型 API、MCP 接入与调试 |
| 求职与面试 | 面试题库 | 按岗位能力域组织 问法 + 答法;对齐同类线上培训结业能力与 JD 高频考点 |
🎯 你学完能收获什么?
- 可上线的项目能力,能独立交付 AI Agent 应用(从环境到部署),从「只会调 API」进阶到能落地的工程实践。
- 体系化的架构表达,能讲清楚 RAG、Agent、MCP 等设计与取舍,面试与简历里经得起追问。
- 面试与 JD 对齐,独立 面试题库,与正文题号互链,按岗位能力域组织问法与答法,适合应届与转岗梳理口径。
- 工程化与简历素材,企业向案例与多路召回、观测、成本等表述,项目可演示、可写进简历。
- 可检索的知识地图,成体系目录 + 案例源码,与常见「智能体 / 应用开发」课表维度对齐,便于对照补缺,少踩「只看过文章没跑过」的坑。
- 明确的岗位对标,可胜任 AI 应用开发工程师、AI Agent 工程师、AI 自动化流程开发及 AI 产品技术负责人等方向;尤其适合前端 / 后端 / 产品等背景转型 AI 与智能体开发。
📚 教程大纲(节选)
这份大纲不是固定目录。后续将伴随 AI 技术栈继续演进,新的核心知识点、新框架和新项目实践会继续并入这条路线中。
01 大模型基础能力构建
| 章节名称 | 内容概要 |
|---|---|
| 大模型(LLM)认识与环境准备 | 起源与发展、AGI 关系、主流模型特点与适用场景、预训练/微调/推理 |
| 大模型架构原理 | Transformer、MoE、自注意力、LLaMA/Qwen/GPT、多模态 |
| 大模型调度平台 | Ollama、私有大模型调用、云端/本地部署(AWS、阿里云等) |
| 提示词工程 | 核心原则与结构、链式思维与 Few-shot、多轮对话与记忆、在 Agent 与工具调用中的应用 |
02 企业低代码平台开发与项目实战
| 章节名称 | 内容概要 |
|---|---|
| Coze(扣子)平台 | 界面与功能、插件/知识库/工作流/智能体、Python 调用工作流 |
| 项目 1:商户运营管家 | 行业调研 PPT、爆款视频复刻、营销海报、卖点提炼、评论分析 |
| Dify AI 平台 | 工作流/Agent/知识库、多案例(投诉分类、调研报告、客服分析、评论分析)、Python 调用 |
| 容器化技术 | Docker 核心概念、安装与常用命令 |
| 企业级大模型部署 | 腾讯云/阿里云、Docker、Dify、AutoDL、Ollama、Xinference、Coze 本地部署 |
| AI 代码编程工具 - Trae AI | 安装使用、多模型 API、MCP 接入 |
| AI 代码编程工具 - Qoder | 使用与调试、项目开发 |
03 大模型核心开发框架
| 章节名称 | 内容概要 |
|---|---|
| LangChain 框架原理与应用 | Model I/O、Prompt、Parser、Runnable / LCEL、Memory、Tools、Retrieval、Agent |
| LangGraph 框架原理与应用 | 图式思维、State/Node/Edge、持久化记忆、流式输出、多智能体与 A2A |
| MCP 从原理到实战 | 与 Function Calling 对比、通信机制、工作流程、Server 部署与自定义开发 |
| 跨 Agent 通信:A2A 协议 | 与 MCP 关系、消息与认证、典型场景 |
04 企业级 RAG / Agent 项目实战
| 章节名称 | 内容概要 |
|---|---|
| 掌柜智库 | LangGraph RAG 工作流、MinerU/OCR、向量+稀疏+Neo4j 多路召回、HyDE/BGE-Rerank、RAGAS 评估 |
| 电商小二 | 意图解析、多源知识库、流式回复、转人工机制、对话复盘、多渠道与监控 |
| 电商问数 | 围绕自然语言问数,完整串起 MySQL 数仓、元数据知识库、Qdrant 向量检索、Elasticsearch 字段值检索、LangGraph 工作流、SQL 生成校验执行、FastAPI SSE 和前后端联调 |
| 深度研搜 | 基于 DeepAgents 搭建多智能体研究系统,串起网络搜索、MySQL 查询、RAGFlow 知识库、文件读取生成、FastAPI 接口和 WebSocket 实时进度回传 |
| 市场罗盘 | 场景化任务拆解、从 0 到 1 设计与开发、阶段目标与进度管控、代码评审与成果展示 |
已完成实战项目推荐: - 电商问数(源码仓库):不是简单的 SQL 生成 Demo,而是把MySQL、LangGraph、Qdrant、Elasticsearch、FastAPI等知识点放进同一条可运行的智能问数链路里。 - 深度研搜(源码仓库):不是普通聊天框,而是围绕开放研究任务,把主智能体调度、子智能体分工、多来源资料检索、文件生成交付和前后端实时联动做成一条完整闭环。
05 大模型微调实践
| 章节 | 内容概要 |
|---|---|
| 28 微调概述 | 问题诊断、模型类型、基线和关键词项目流程 |
| 29 数据与模板 | 真实 JSONL、样本质量、清洗划分和对话模板 |
| 30 训练与高效微调 | 训练参数、显存算例、LoRA 与 QLoRA |
| 31 LLaMA-Factory 微调实战 | AutoDL 单卡 / FP16、清洗版训练、日志与备份 |
| 32 微调效果评估与模型部署 | 单条验证、200 条双组预测与评分、导出与独立加载;vLLM 选做 |
| 33 微调显存优化与多卡训练 | 单卡显存排查、并行分工、ZeRO 与短跑日志判断 |
06 大厂开发规范
| 章节名称 | 内容概要 |
|---|---|
| 企业大模型研发流程 | 技术调研、方案与框架设计、RAG 与 pipeline、评估与角色、立项与需求文档 |
| 大模型当下热点 | Agent/RAG 主流技术、前沿与热点跟踪 |
🏗️ Agent 项目架构与技术架构

🚀 快速开始
结合 在线文档 一起学习。想马上跑通一个案例?按下面几步即可。更详细的环境说明、API 申请、常见报错处理见 新手入门与常见问题。
- 克隆仓库并进入项目目录
git clone https://github.com/didilili/ai-agents-from-zero.git
cd ai-agents-from-zero
- 准备环境(推荐 Python 3.10,支持 3.10–3.13)
python3.10 -m venv .venv
source .venv/bin/activate # macOS/Linux
# .venv\Scripts\activate # Windows CMD
pip install -r requirements.txt
- 配置 API Key
- 将根目录下的
.env-example复制为.env - 在
.env中填入你的 API Key(如通义千问/阿里百炼、DeepSeek 等),变量名需与代码一致(如aliQwen-api、QWEN_API_KEY、deepseek-api) - 各平台 Key 的申请方式见 新手入门与常见问题 - 各 API 平台如何申请 Key
- 将根目录下的
- 在项目根目录运行第一个案例
python 案例与源码-2-LangChain框架/01-helloworld/StandardDesc.py
建议:在项目根目录执行 python,便于统一管理虚拟环境、依赖和输出文件。普通脚本中的 load_dotenv() 通常会自动向上查找 .env;如果你使用 IDE、REPL 或调试器且读取不到 Key,请检查其工作目录配置。若不想用云 API,可使用 Ollama 本地模型(无需 Key)。
遇到 ModuleNotFoundError、API Key 报错、找不到 .env 等,请查看 新手入门与常见问题 - 常见问题与解决。
💬 交流社群
想和更多读者共同交流 AI 编程、Agent / RAG 实战、面试经验?欢迎到 GitHub 入群申请帖 留言申请加入微信交流群。
📖 关于本仓库
- 目标:做一套真正适合入门、系统、且长期更新的 AI 智能体实战速成教程,不仅把概念讲清楚,也把案例跑起来,让你从 0 到能独立做 RAG / Agent / 多智能体 类项目,并能用工程化语言讲清楚自己的方案与项目。
- 技术定位:聚焦 Python 智能体开发路线,重点讲 LangChain / LangGraph 及相关工程实践,不走 Spring AI / langchain4j 的 Java 路线,更适合想直接进入 Python 大模型应用开发的同学。
- 教程来源:参考尚硅谷《大模型智能体速成班》等课程资料,并在此基础上结合公开文档、社区实践与项目经验持续重构、补充与维护,逐步整理成一套 Python 智能体应用开发 的系统化学习资料。
- 面试题来源:题库中有相当一部分题目整理自大厂真实面试题、公开面经与高频追问场景,并结合本仓库的章节主线做了工程化重构,更适合按项目和系统设计视角复习。
- 内容构成:系统章节笔记 + 可运行案例源码 + 面试题库 (对标同类线上培训与社招/校招 JD)。
⭐ Star History
[]()
仓库英文名:ai-agents-from-zero · 仓库中文名:《AI 智能体实战速成指南:从零到企业级落地》
教程更新日志
教程更新日志
本仓库将伴随 AI 大模型技术栈持续进化持续更新,永不止步!✨✨
2026-09-28
仓库与工程
- 修复部分 LangChain 示例在不同目录运行时找不到 Prompt 和 RAG 资源文件的问题,并补充 .env 配置与路径排查说明(感谢 @urpapru 在 PR #92 中提供的改进建议~~)
2026-09-10
「大模型微调实践」第 28~33 章现已全部更新完成! 这次终于可以把完整的微调篇交到大家手中了。✨
先和大家认真说声抱歉。因为工作原因,教程搁置了很长一段时间,之前说好的更新也没能按时完成,让一直关注、等待更新的朋友们久等了。感谢大家的耐心,也谢谢这段时间仍在阅读、提问和反馈问题的同学。🙏
这次主要的时间和精力都投入到了微调篇。从概念讲解,到数据整理、实机训练,我反复打磨了很多轮。希望刚接触微调的同学,能够理解每一步为什么要做,跟着完成一次训练,并学会判断效果、定位问题和继续改进。
教程与文档
本次重写第 28 章,新增第 29~33 章,以「中文关键词抽取」为贯穿案例,把数据准备、训练、评估与模型交付串成一条完整的学习路线:
- 第 28 章「大模型微调概述与整体流程」:从具体问题出发,判断何时需要微调,理解模型选择、任务约定与基线比较。
- 第 29 章「微调数据准备与对话模板」:讲清 JSONL、Alpaca / ShareGPT、样本审核、清洗去重、数据划分与对话模板,并加入文档问答数据制作练习。
- 第 30 章「模型训练原理与高效微调」:用具体例子理解 Loss、梯度、批次、学习率、显存与数值精度,再学习 LoRA / QLoRA、训练日志与检查点。
- 第 31 章「LLaMA-Factory 环境搭建与微调实战」:跟着 AutoDL 单卡案例完成环境准备、数据登记、参数配置、训练启动、日志检查和结果备份。
- 第 32 章「微调效果评估与模型部署」:对照原始模型与微调模型的回答,完成批量预测、关键词评分、错误分析与回归检查,学习模型合并导出,并提供 vLLM 部署与接口调用的选做内容。
- 第 33 章「微调显存优化与多卡训练」:学习显存不足的排查方法,比较不同配置的显存与耗时,理解三种并行方式、DeepSpeed ZeRO,以及大模型试跑时的取舍。
仓库与工程
- 修复站点搜索异常,更新搜索索引缓存。
接下来,教程恢复周更。 我会继续推进面试习题的更新,也会根据大家的反馈修正和完善已有章节。再次感谢大家的理解、等待和支持,我们继续一起学下去!
2026-06-22
最近因为工作原因,教程停更了大半个月,今天开始恢复更新啦。感谢一直还在等更新的朋友们 🙏 接下来几天会陆续更新「大模型微调实践」系列内容,交流群也会在近日正式成立。与时俱进,和 AI 共同前行,不是说说而已~~
教程与文档
- 新增「大模型微调实践」教程章节:第 28 章 大模型微调概述与整体流程
2026 年 6 月更新计划(历史记录)
先和大家说声抱歉:最近在开发另一个可能对你 AI 编程有帮助的网站,所以教程更新节奏稍微慢了一点…😭😭
本月,本教程会迎来一次系统性重大升级。它不只是单纯对现有知识点进行补充完善,而是围绕学习路线、面试复习和在线阅读体验等进行整体优化。教程也会继续坚持 免费开源,系统路线,长期更新 的方向,让你更加轻松自如地学习 AI Agent。
本月重点
- 增加微调章节:从基础概念到 LoRA/QLoRA、量化、模型导出与 vLLM 部署,带你完整跑通一次轻量级微调流程。
- 建立微信交流群:你想要的微信交流群终于要来啦~~~想入群的朋友可以到 GitHub 入群申请帖 留言申请。希望它不只是一个教程交流群,而是一个活跃、有价值的程序员 AI 交流社区,让我们一起,伴随 AI 的不断升级一起成长。
- 推进实战项目「电商小二」:计划 5 月底完成的项目确实拖更了(我的问题…),本月将继续推进,这次一定把它补上,把实战项目继续推进完整。
- 全面升级面试题库:重新梳理现有面试题库,补充更多大厂面经,方便大家系统复习。
- 改版教程站点界面:随着教程内容日渐增多,现阶段的
Docsify已经有些不够用了;后续计划使用VitePress重构站点,并重新梳理学习路线,让导航、搜索和阅读体验都更加得心应手。
这些内容会在 6 月陆续更新。谢谢大家的关注与等待~~
2026-05-28
教程与文档
- 深度重构第 8 章「Docker 快速入门与 Dify 部署排障」,补充 MySQL 容器实战、Dockerfile、Compose 多服务编排等内容
2026-05-27
仓库与工程
- 更新
README.md项目定位,AI 不停,更新不止,一路成长~~✨✨
2026-05-26
教程与文档
- 围绕 9 ~ 21 章 LangChain 相关章节进行集中更新修订,梳理 LangChain v1.x 主线内容,补充关键配图与
Mermaid流程图。不断完善,不断进步~~- 第 11 章「Model I/O 与模型接入」:补充
init_chat_model、OpenAI 兼容接口、流式输出、多模型切换与返回对象理解 - 第 13 章「提示词与消息模板」:补充
ChatPromptTemplate、消息结构、MessagesPlaceholder与 Few-shot 示例组织 - 第 14 章「输出解析器」:补充普通解析器、结构化输出、Pydantic 校验与
with_structured_output的选型边界 - 第 15 章「LCEL 与链式调用」:补充
Runnable统一接口、并行编排、透传、重试、fallback 与 LCEL 工程写法 - 第 18 章「向量数据库与 Embedding 实战」:补充稠密向量、稀疏向量、混合检索、HNSW、向量写入与检索流程
- 第 19 章「RAG 检索增强生成」:补充加载、切分、向量化、索引、召回、重排、生成、引用来源与高级 RAG 优化思路
- 第 21 章「Agent 智能体」:补充
create_agent、Agent 工作循环、短期记忆、LangSmith 观察、人类审核与 MCP 工具接入
- 第 11 章「Model I/O 与模型接入」:补充
2026-05-25
教程与文档
- 围绕 22 ~ 27 章 LangGraph 相关章节进行集中更新修订,优化文字表述,新增关键配图与
Mermaid流程图。不断完善,不断进步~~- 第 23 章「LangGraph API:图与状态」:补充输入输出隔离、
Reducer选型、Actor/Channel、Superstep与StateSnapshot等内容。 - 第 25 章「LangGraph 高级特性」:补充
checkpoint、断点恢复、持久化后端选型和interrupt恢复与幂等处理等图示 - 第 26 章「LangGraph 多智能体与 A2A」:新增 MCP 与 A2A 对比、
Supervisor执行流程等配图
- 第 23 章「LangGraph API:图与状态」:补充输入输出隔离、
- 优化实战项目「深度研搜」与「电商问数」教程章节,新增关键配图与
Mermaid流程图
2026-05-23
教程与文档
- 围绕 1-1 ~ 2章 基础认知相关章节进行集中更新修订,优化文字表述,新增关键配图与
Mermaid流程图。不断完善,不断进步~~- 第 1-1 章「大模型认知与工程概览」:补充大模型发展脉络、
Transformer架构演进 - 第 1-2 章「提示词工程基础」:补充提示词结构、上下文组织、角色设定内容
- 第 1-3 章「RAG、微调、续训与智能体选型」:完善 RAG、微调、续训、智能体之间的边界与选型逻辑
- 第 2 章「RAG 搭建企业私有&个人知识库」:补充 RAG 流程、切分、生成与评估链路
- 第 1-1 章「大模型认知与工程概览」:补充大模型发展脉络、
2026-05-22
教程与文档
- 修正实战项目「深度研搜」错误文件命名(感谢@Sheldon-MMMP 同学反馈~~)
- 修正 第 1-1 章:大模型认知与工程概览 图片展示不全问题(感谢@Hunter-Lam 同学反馈~~)
仓库与工程
- 新增
Mermaid扩展,支持添加缩放、拖拽与全屏查看,提升图表阅读体验
2026-05-17
教程与文档
- 新增实战项目「深度研搜」教程章节:第 13 章 主智能体搭建与异步执行、第 14 章 FastAPI 接口与项目闭环
- 实战项目「深度研搜」正式宣布更新完成~~✨✨
2026-05-16
教程与文档
- 新增实战项目「深度研搜」教程章节:第 12 章 RAGFlow 部署、模型配置与知识库准备
- 重构第 6 章「6-Coze与Dify的Windows平台部署」
仓库与工程
- 新增
docsify-darklight-theme插件,支持白天 / 夜间模式切换 - 优化全书各章节「学习建议」与「章节思考题」,提升阅读引导与复盘效果
2026-05-15
教程与文档
- 新增实战项目「深度研搜」教程章节:第 11 章 数据库查询子智能体与 MySQL 工具
2026-05-14
教程与文档
- 新增实战项目「深度研搜」教程章节:第 8 章 项目总览与工程初始化、第 9 章 基础模块与模型配置、第 10 章 网络搜索子智能体与 Tavily 工具
仓库与工程
- 重构「全书术语表」文档,更好理解,更加强大!
2026-05-13
教程与文档
- 新增第 27 章 Skills 技能与 AI 编程工具实践,重构 Skills 相关内容
- 完善实战项目「深度研搜」教程章节:第 7 章 中间件机制与 Skills 配置
仓库与工程
- 新增支持 Docsify 渲染 Mermaid 图表
2026-05-12
教程与文档
- 新增实战项目「深度研搜」教程章节:第 7 章 DeepAgents 中间件机制与调用限制
2026-05-11
教程与文档
- 新增实战项目「深度研搜」教程章节:第 5 章 人机协作与中断恢复、第 6 章 长期记忆与 Backend 存储
2026-05-10
仓库与工程
- 重构仓库中所有关于 Docker 的内容表述,大幅提升可读性
- 更新仓库中过时模型、工具、部署与现代智能体场景内容,与时俱进
2026-05-09
教程与文档
- 新增实战项目「深度研搜」教程章节:第 1 章 DeepAgents 基础与核心概念、第 2 章 DeepAgents 快速入门与流式解析、第 3 章 子智能体进阶与异步执行、第 4 章 接入 LangGraph 与 LangChain
仓库与工程
- 今日正式开启实战项目「深度研搜」更新,预计在 5 月 20 日更新完成~~✨✨
2026-05-07
仓库与工程
- 完善「新手入门与常见问题」文档,对首次阅读仓库的同学更加友好~
- 优化实战项目「电商问数」开发环境的相关表述
2026-05-03
教程与文档
- 新增实战项目「电商问数」教程章节:第 17 章 前后端联调与日志追踪
- 实战项目「电商问数」正式宣布更新完成~~✨✨
2026-04-29
教程与文档
- 新增实战项目「电商问数」教程章节:第 15 章 API 接口基础与 FastAPI 入门、第 16 章 查询接口实现与依赖组装
2026-04-27
教程与文档
- 新增实战项目「电商问数」教程章节:第 13 章 SQL 生成前的信息过滤与补全、第 14 章 SQL 生成与执行闭环
2026-04-26
教程与文档
- 新增实战项目「电商问数」教程章节:第 11 章 关键词抽取与多路召回、第 12 章 召回信息合并与上下文构建
2026-04-25
教程与文档
- 新增实战项目「电商问数」教程章节:第 10 章 问数智能体总览与工作流骨架
2026-04-24
仓库与工程
- 优化部分文档的口语化表达,进一步提升可读性
2026-04-23
教程与文档
- 新增实战项目「电商问数」教程章节:第 9 章 字段检索能力构建
2026-04-22
教程与文档
- 新增实战项目「电商问数」教程章节:第 8 章 表与字段信息同步到元数据库
2026-04-20
教程与文档
- 新增实战项目「电商问数」教程章节:第 7 章 元数据知识库总览与构建入口
2026-04-19
教程与文档
- 新增实战项目「电商问数」前言,为上线做准备
- 完善实战项目「电商问数」教程章节:第 1 章 项目概述与数仓基础、第 2 章 项目整体架构与智能体流程,提升可读性,让你掌握更加轻松~✨✨
2026-04-18
教程与文档
- 新增实战项目「电商问数」教程章节:第 5 章 Qdrant 与 ES 快速入门与接入、第 6 章 MySQL、Embedding 与日志管理
2026-04-16
教程与文档
- 新增实战项目「电商问数」教程章节:第 3 章 开发环境与基础服务准备、第 4 章 项目结构与基础服务配置管理
2026-04-15
教程与文档
- 新增实战项目「电商问数」教程章节:第 1 章 项目概述与数仓基础、第 2 章 项目整体架构与智能体流程
2026-04-13
仓库与工程
- 新增「工具导航与参考资料索引」文档,汇总全书各章节涉及的官方文档、常用工具与延伸阅读链接
2026-04-12
教程与文档
- 重构第 2~8 章文档(2-RAG-搭建企业私有&个人知识库、3-基于Coze&Dify平台的智能体开发、4-Python调用Dify平台工作流、5-Python调用Coze平台工作流、6-Coze与Dify的Windows平台部署、7-Dify的Windows平台部署、8-企业级大模型部署)
仓库与工程
- 规范文档中的图片渲染最高尺寸,大幅提升用户阅读体验
- 修正部分图片无法正常放大的问题
2026-04-11
教程与文档
- 重构第 1-1~1-3 章文档(1-1-大模型认知与工程概览、1-2-提示词工程、1-3-RAG、微调、续训与智能体)
仓库与工程
- 新增
KaTeX + docsify-katex数学公式渲染支持,支持 Markdown 中的行内公式$...$与块级公式$$...$$,修正部分文档的公式展示问题
2026-04-08
仓库与工程
- 实战项目已开启疯狂更新模式,预计下周将更新第一个实战项目——掌柜智库,一如寄往坚守深入浅出,通俗易懂的原则,敬请期待!🔥🔥✨✨
2026-04-07
仓库与工程
- 优化「README.md」介绍,凸显本仓库特点:聚焦 Python 生态,拒绝 Java 绕路
- 全面重构「面试题库」:目录更顺、问法与答法更成体系,和教程章节、学习路线更好对齐 💪🎉
- 历时近 10 天的 LangChain 与 LangGraph 章节重构,至今已全面完成~新教程更加易读,更成体系!✨🙌
2026-04-06
教程与文档
- 重构第 25~26 章文档(25-LangGraph高级特性、26-LangGraph多智能体与A2A)
- 优化所有章节底部的「章节小结」,新增「章节思考题」,帮助读者更好地理解与掌握知识点
仓库与工程
- 更新「教程目录大纲」
- 修正部分章节的序号错误问题
- 新增「全书术语表」,汇总正文中高频中英文术语与缩写,便于随查随用
2026-04-05
教程与文档
- 重构第 23~24 章文档(23-LangGraphAPI:图与状态、24-LangGraphAPI:节点、边与进阶)
2026-04-04
教程与文档
- 重构第 22 章文档(22-LangGraph概述与快速入门)
2026-04-01
教程与文档
- 重构第 18~21 章文档(18-向量数据库与Embedding实战、19-RAG检索增强生成、20-MCP模型上下文协议、21-Agent智能体)
2026-03-31
教程与文档
- 重构第 15~17 章文档(15-LCEL与链式调用、16-记忆与对话历史(含Redis基础)、17-Tools工具调用)
仓库与工程
- 修正 3.10 案例图片无法正常显示的问题(感谢@jupenhauer 同学反馈~~)
2026-03-30
教程与文档
- 重构第 9~13 章节文档(9-LangChain概述与架构、10-LangChain快速上手与HelloWorld、11-Model-I-O与模型接入、12-Ollama本地部署与调用、13-提示词与消息模板)
2026-03-29
教程与文档
- 梳理第 14 章节文档,进行逐一校对实测,提升可读性与参考价值
2026-03-27
教程与文档
- 梳理第 13 章节文档,进行逐一校对实测,提升可读性与参考价值
2026-03-26
教程与文档
- 梳理第 9~12 章节文档,进行逐一校对实测,提升可读性与参考价值
2026-03-25
教程与文档
- 完善 第 26 章:LangGraph 多智能体与 A2A,以及相关案例源码
2026-03-24
教程与文档
- 完善 第 25 章:LangGraph 高级特性,以及相关案例源码
- 完善 第 26 章:LangGraph 多智能体与 A2A,以及相关案例源码
2026-03-23
教程与文档
- 完善 第 22 章:LangGraph 概述与快速入门,以及相关案例源码
- 完善 第 23 章:LangGraph API:图与状态,以及相关案例源码
- 完善 第 24 章:LangGraph API:节点、边与进阶,以及相关案例源码
2026-03-22
教程与文档
- 规范 第 23~26 节的图片命名
2026-03-21
仓库与工程
- 优化「README.md」介绍,全面对标相关培训课表
2026-03-20
教程与文档
- 完善智能体与大模型应用相关面试题汇编
2026-03-19
教程与文档
- 新增智能体与大模型应用相关面试题汇编
2026-03-18
仓库与工程
- 接入 Giscus 评论系统(基于 GitHub Discussions)
- 接入 Google Analytics 4(GA4),用于统计每页访问量
2026-03-17
教程与文档
- 完善 第 24 章:LangGraphAPI:节点、边与进阶,以及相关案例源码
- 完善 第 25 章:LangGraph 高级特性,以及相关案例源码
- 完善 第 26 章:LangGraph 多智能体与 A2A,以及相关案例源码
2026-03-16
教程与文档
- 完善 第 23 章:LangGraph API:图与状态,以及相关案例源码
仓库与工程
- 优化案例与源码的输出示例的注释方式
- 新增「快速开始」说明
2026-03-15
教程与文档
- 新增 第 22 章:LangGraph 概述与快速入门,以及相关案例源码
- 新增 第 23 章:LangGraph API:图与状态,以及相关案例源码
- 新增 第 24 章:LangGraph API:节点、边与进阶,以及相关案例源码
仓库与工程
- 统一案例与源码路径并修正教程目录大纲链接
2026-03-14
仓库与工程
- 修订 LangChain 教程第 9-21 章,修正错别字,新增知识脑图(强化章节递进)
2026-03-13
教程与文档
- 完善 第 20 章:MCP 模型上下文协议,以及相关案例源码
- 完善 第 21 章:Agent 智能体,以及相关案例源码
仓库与工程
- 更新「教程目录大纲」
2026-03-12
教程与文档
- 新增 第 20 章:MCP 模型上下文协议,以及相关案例源码
- 新增 第 21 章:Agent 智能体,以及相关案例源码
仓库与工程
- 在「新手入门与常见问题」文档中,新增本项目所使用的 Docsify 扩展说明
2026-03-11
教程与文档
- 完善 第 18 章:向量数据库与 Embedding 实战,以及相关案例源码
- 完善 第 19 章:RAG 检索增强生成,以及相关案例源码
仓库与工程
- 固定 Python 版本为 3.10,langchain-redis 等依赖的兼容问题,将
requirements.txt、pyproject.toml、新手入门与常见问题、.python-version同步更新 - 新增依赖 langchain-redis、pypdf、unstructured、python-docx、docx2txt、markdown、jq、langchain-unstructured 等,用于 RAG 文档相关案例执行
- README 底部新增 Star History 星标历史图表
- 新增 zoom-image(图片点击放大)、docsify-sitemap(GitHub Actions 自动生成 sitemap.xml)、docsify-count(字数统计)等 Docsify 扩展
2026-03-10
教程与文档
- 新增 第 18 章:向量数据库与 Embedding 实战,以及相关案例源码
- 新增 第 19 章:RAG 检索增强生成,以及相关案例源码
2026-03-09
教程与文档
- 完善 第 16 章:记忆与对话历史,以及相关案例,新增 Redis Stack 知识点与案例
- 完善 第 17 章:Tools 工具调用,以及相关案例
2026-03-06
教程与文档
- 完善 第 15 章:LCEL 与链式调用,以及相关案例
仓库与工程
- 引入 Black 统一 Python 代码风格:新增
pyproject.toml配置 Black(行宽 88、排除 .venv 等),requirements.txt增加black、grandalf,全库.py已用 Black 格式化 - 新增
.vscode/settings.json与extensions.json,支持保存时自动格式化并推荐 Black Formatter 扩展
2026-03-04
教程与文档
- 完善 第 15 章:LCEL 与链式调用,以及相关案例
2026-02-27
教程与文档
- 进一步完善 第 14 章:输出解析器,相关案例补充输出示例
- 新增 第 15 章:LCEL 与链式调用,以及相关案例源码
- 新增 第 16 章:记忆与对话历史,以及相关案例源码
- 新增 第 17 章:Tools 工具调用,以及相关案例源码
仓库与工程
- 新增「新手入门与常见问题」文档,面向零基础同学说明环境准备、运行案例、API Key 申请及常见报错处理
- 新增
requirements.txt与.env-example,统一项目案例依赖与环境变量示例 - 修正部分文档中加粗格式未格式化的问题
- 统一案例源码文件头部说明格式,补充完善「对应教程章节」与「知识点速览」
2026-02-26
教程与文档
- 完善 第 11 章:提示词与输出解析,完善输出解析器部分内容,以及相关案例(已并入新 13、14 章)
仓库与工程
- 大模型核心开发框架 章节拆分:原 9、10、11 三个文档按推荐方案一拆分为 6 个文档(9 ~ 14),更加方便读者阅读
- 将 Docsify 代码高亮主题调整为 GitHub 风格浅色(prism-ghcolors),文档内代码块展示更贴近 GitHub 阅读体验
- 案例源码展示方式统一,将第 10 ~ 14 章对应「案例与源码」文件夹内的代码改为 Docsify
:include :type=code形式直接嵌入文档,在文档内即可看到与仓库同步的最新源码,后期都将调整为这种形式啦~
2026-02-25
今日是 2026 年开工「第一天」(实际是第 2 天,假装是第 1 天),祝各位开工大吉!🎉
顺便祝各位:Bug 少少、需求一次过、代码写得顺、摸鱼不被发现~ 💻
教程与文档
- 完善 第 11 章:提示词与输出解析,完善对话提示词模板和文本提示词模板部分内容,以及相关案例
2026-02-15
教程与文档
- 完善 第 11 章:提示词与输出解析,新增相关案例源码
2026-02-14
教程与文档
- 完善 第 1-1 章:大模型认知与工程概览,补充大模型的分类知识
- 完善 第 10 章:Model-I-O 与 Ollama 本地部署
2026-02-13
仓库与工程
- 规范全局文档的子级标题序号,修正部分文档标题序号不规范的问题
- 左侧侧边栏目录从三级标题调整至展示四级标题,提升阅读体验
2026-02-11
今日小年,提前祝各位朋友马年大吉,工作顺利!🎉🧧🐴✨
教程与文档
- 完善 第 9 章:LangChain 概述与快速上手,新增相关案例源码
- 完善 第 10 章:Model-I-O 与 Ollama 本地部署,新增相关案例源码
仓库与工程
- 将第 1 章 大模型智能体概述 拆分为三个文档,并且补充相关总结,便于学习
- 优化本仓库所有文档序号,并增加每个章节的「课程目标」与「章节小结」,提升阅读体验
2026-02-10
教程与文档
- 新增 第 11 章:提示词与输出解析
- 新增 第 10 章:Model-I-O 与 Ollama 本地部署
- 完善 第 9 章:LangChain 概述与快速上手
2026-02-09
教程与文档
- 完善 第 8.1 章:Docker 入门与 Dify 部署常见问题,增加大量 Docker 相关知识点,并进行逐一校对实测,提升可读性与参考价值
- 完善 第 9 章:LangChain 概述与快速上手
2026-02-05
教程与文档
- 新增 第 9 章:LangChain 概述与快速上手,正式开启「大模型核心开发框架」章节部分更新~~
- 修正 所有文档的表达不准确的文字和病句,进一步优化细节处理
- 完善 第 8 章:企业级大模型部署
仓库与工程
- 去除 README 中课程大纲的笔记跳转,改为在教程目录大纲.md 中增加跳转超链接
2026-02-04
教程与文档
- 新增 第 4–8 章:智能体集成与部署相关教程
- 完善 第 3.6–3.10 章案例文档,补充概要(使用的插件、技术要点、流程概览)
仓库与工程
- 新增「教程更新日志」文档,记录每天一点一滴的成长与进步~~
2026-02-03
教程与文档
- 完善 第 3.1–3.6 章案例文档,补充概要(使用的插件、技术要点、流程概览)
仓库与工程
- 更新 仓库标题,将《AI 智能体从入门到精通》,这种千篇一律的大众名称,更名为《AI 智能体实战速成指南:从零到企业级落地》,更加贴合教程内容,焕然一新!
- 优化 README:修正错误的超链接跳转,优化仓库描述文字
2026-02-02
教程与文档
- 新增 工作流智能体案例 第 3.1 ~ 3.10 章
仓库与工程
- 接入 Docsify 静态网站,支持 GitHub Pages 与本地预览,便于大家在线阅读
- 优化 README:补充在线阅读链接,增加顶部导航、调整章节标题与顺序,新增徽章展示
2026-02-01
教程与文档
- 新增 第 3 章:基于 Coze&Dify 平台的智能体开发
仓库与工程
- 引入 commitlint 与 husky 规范 commit message
- 统一项目文档命名,去掉前导零,并更新图片引用与 alt
- 优化 README:优化教程目录,已更新章节改为可跳转笔记链接,完善教程介绍与亮点,新增生态架构图与教程更新情况
- 新增 课程案例链接汇总 文档,增加此目录索引,未来找案例更加方便啦~~
2026-01-30
教程与文档
- 新增 第 2 章:RAG-搭建企业私有&个人知识库
- 完善 第 1 章,补充模型分类说明
2026-01-29
教程与文档
- 新增 第 1 章:大模型智能体概述
仓库与工程
- 首次提交:新增 README、课程目录大纲;根据尚硅谷的相关课程整理补充汇总(再次感谢,受益匪浅),仓库正式开启啦~~
- 添加 .gitignore,忽略 .DS_Store 等系统与临时文件
AI智能体与大模型应用开发面试题库
AI 智能体与大模型应用开发面试题库
定位:面向 AI 智能体 / 大模型应用开发工程师、AI 应用开发工程师、Agent 开发工程师。 风格:偏 应用落地、架构设计、工程治理、项目实战,不偏纯算法研究或预训练细节。
文档特点:
- 每道题都标注了重要度和难度,方便区分必刷内容、常考内容和进阶内容。
- 题目下方尽量补充了对应章节,方便回看知识点、边学边练。
- 相当一部分题目来自大厂真实面经和实际高频追问场景,更贴近真实面试。
- 整体表达坚持语言专业但易读,尽量避免空话、套话和纯概念堆砌。
- 各个二级标题按主题分类,结构清晰,便于按模块系统复习。
重要度标记说明:
必刷:大概率会被问到,答不好会直接暴露短板。高频:面试中很常见,通常是主问题或高频追问。中频:更偏工程设计、项目深挖和落地细节。低频:加分题、延伸题、岗位深挖题。
难度标记说明:
基础:偏概念主干和标准答法,适合第一轮打底。中等:需要结合工程理解、分层表达或一定项目经验。较难:更偏系统设计、复杂治理、协议细节或高级架构取舍。
1、基础认知与技术选型
Q1-1. RAG 和微调(SFT)的核心区别是什么?能不能一起用?
RAG 解决的是“知识从哪里来”,微调解决的是“模型应该怎么表现”。
RAG 不改模型参数,而是在运行时把相关资料检索出来,再交给模型生成答案,所以更适合处理时效性强、更新频繁、需要可溯源的知识。微调会改模型参数,更适合提升输出风格一致性、结构化能力、分类能力或领域行为稳定性。
它们当然可以一起用,而且很多企业项目就是这么落地的:用 RAG 提供知识,用微调或强约束输出提升行为稳定性。比如客服项目里,产品政策靠 RAG 保持最新,回复格式和字段输出靠微调或结构化输出保证稳定。
常见追问:
- 为什么多数企业项目不会优先做续训?
- 什么场景适合
RAG + 微调组合? - 如果预算有限,先做哪一个?
重要度:必刷(出现次数:3次) | 难度:中等
考察点:知识增强和行为优化的区分能力。
对应章节:1-3 RAG、微调、续训与智能体选型 §1、RAG;1-3 RAG、微调、续训与智能体选型 §2、微调(Fine-tuning)
Q1-2. 什么是 AI Agent?它和传统 AI 应用的区别是什么?
可以概括为:Agent = 模型 + 工具集 + 运行循环 + 当前状态。
- 模型负责理解目标和决定下一步。
- 工具集负责提供外部能力。
- 运行循环负责让系统不是只回答一次,而是能“想一步、做一步、看结果、再决定下一步”。
- 当前状态负责保存消息、工具结果和中间结论。
传统 AI 应用更接近“输入一个请求,返回一个结果”的单轮系统;Agent 更接近“围绕目标持续做决策”的运行方式。它的关键不只是用了大模型,而是具备了工具调用、状态推进和动态决策能力。
所以不是所有 AI 应用都该叫 Agent。固定表单生成、固定知识问答、固定流程审批,这类系统往往用普通链路或 Workflow 就够了。只有在路径不固定、需要根据中间结果继续选择工具或调整路线时,Agent 才真正合适。
另外也要注意,很多人会把“长期记忆、复杂规划、多智能体协作”都当成 Agent 的必备条件,不是。对很多入门场景来说,能做到“理解目标、决定是否调工具、拿到结果再继续判断”,就已经是 Agent 了。
重要度:高频(出现次数:2次) | 难度:基础
考察点:是否理解 Agent 的本质,不会把任何聊天机器人都叫 Agent。
对应章节:21 Agent智能体 §1、Agent 简介;1-3 RAG、微调、续训与智能体选型 §4、智能体开发
Q1-3. 什么时候该用 Prompt、RAG、微调、Workflow、Agent?
最稳的判断方式,是先判断问题到底是缺知识、缺行为,还是缺流程能力。
- 如果模型本来就会,只是任务没描述清楚,先用 Prompt。
- 如果缺的是私有知识、最新知识、外部知识,优先上 RAG。
- 如果缺的是稳定行为,比如固定格式、固定语气、固定分类习惯,再考虑微调。
- 如果流程固定、节点明确、强调稳定和可审计,优先 Workflow。
- 如果路径不固定,需要边做边决定下一步、动态调用工具,再考虑 Agent。
这条选型主线可以压缩成一句话:
- Prompt 解决任务表达问题。
- RAG 解决知识来源问题。
- 微调 解决行为稳定问题。
- Workflow 解决固定流程问题。
- Agent 解决动态决策问题。
真实项目里通常不会一开始就说“必须做 Agent”。大部分需求先用 Prompt + RAG + Workflow 就能覆盖,只有在任务开放度高、工具选择依赖中间结果、流程没法提前写死时,Agent 的价值才会真正体现出来。
常见追问:
- 为什么很多项目不一开始就做微调?
- 为什么不是所有复杂任务都适合 Agent?
- Workflow 和 Agent 的分界线是什么?
重要度:必刷(出现次数:1次) | 难度:中等
考察点:技术选型能力、边界意识、是否有工程判断。
对应章节:1-3 RAG、微调、续训与智能体选型 §1、RAG;1-3 RAG、微调、续训与智能体选型 §4、智能体开发
Q1-4. Token 和上下文窗口是什么?它们在工程上意味着什么?
Token 可以看作模型处理文本的基本计量单位,不等于“字数”或“单词数”。模型在训练和推理时看到的不是原始文本,而是 tokenizer 切出来的一串 token。
上下文窗口则是模型单次推理时最多能看到多少 token 的上限。工程上常说的 8K、32K、128K,本质上说的都是“这次请求里系统提示、历史对话、检索结果、用户问题加起来最多能塞多少 token”。
它的工程意义非常直接:
- 上下文窗口决定了你能不能做长文问答、多轮对话和大段 RAG 上下文拼接。
- token 越多,成本通常越高,延迟也更容易上升。
- 超出窗口的内容会被截断、压缩或根本进不了模型,所以不是“塞得越多越好”。
如果只做一个大致口径,128K token 可以看作很长的一段内容。按常见经验口径,1 个中文字符大约是 0.6 个 token,1 个英文字符大约是 0.3 个 token,所以 128K token 粗略可以近似成二十万级别的中文字符量级。但这只是估算,真实数字会因为 tokenizer、语言类型、代码、标点和格式不同而波动。
常见追问:
- 1 个汉字、1 个英文单词大概是多少 token,为什么不能死记一个固定值?
- 如果上下文被截断了,你会优先怎么处理?
重要度:高频(出现次数:1次) | 难度:基础
考察点:是否真正理解 token、上下文窗口和成本 / 截断 / 长文本场景之间的关系。
对应章节:1-1 大模型认知与工程概览 §1、认识大模型;11 Model I/O 与模型接入 §2、LangChain 模型分类、参数与返回
来源:腾讯AI后台工程师一面(实际大厂面试题)
Q1-5. 大模型应用上线前你最关心哪些工程指标?
通常会从效果、性能、成本、稳定性、治理性五个维度看。
- 效果:准确率、忠实度、任务完成率、引用正确率。
- 性能:首 token 延迟、端到端延迟、P95/P99。
- 成本:token 消耗、检索成本、工具调用成本。
- 稳定性:超时率、错误率、重试率、降级命中率。
- 治理性:日志、Trace、Prompt 版本、索引版本、权限审计。
如果是 RAG / Agent 项目,我还会额外看检索召回质量、工具调用成功率、人工兜底比例和 badcase 回灌效率,因为这些才是真正影响线上体验的关键指标。
常见追问:
- 你怎么设计降级策略?
- 如果成本超预算,先优化哪一层?
- 怎样做 Prompt / 模型 / 索引回滚?
重要度:高频(出现次数:1次) | 难度:中等
考察点:是否具备生产环境意识。
对应章节:1-1 大模型认知与工程概览 §4、大模型的工程实现(概览);7 企业级大模型部署 §1、企业级大模型部署概述
2、Prompt、结构化输出与护栏
Q2-1. System Message 和 User Message 应该怎么分工?
System Message 负责放稳定约束,User Message 负责放动态任务。
System 里一般放角色、目标、边界、安全规则、输出格式要求、工具使用规范,这些内容应该尽量稳定、少改。User Message 放用户当次任务、输入数据、补充上下文和个性化要求。
这样拆分的好处是结构更清晰,便于版本管理,也更适合后续做模板化、灰度和评测。如果把所有内容都混在一段 prompt 里,后面排查效果波动会非常困难。
重要度:必刷(出现次数:1次) | 难度:基础
考察点:Prompt 分层设计能力。
对应章节:13 提示词与消息模板 §3、入参的消息类型;13 提示词与消息模板 §7、对话提示词模板(ChatPromptTemplate)
Q2-2. 结构化输出有哪些常见做法?各自的优缺点是什么?
从 Model I/O 这条主线看,结构化输出大致可以分成“后处理解析”和“前约束结构化输出”两类。
- 纯 Prompt 约束:直接要求模型按 JSON 返回。优点是最简单、起步快;缺点是模型多说一句话、少个逗号,程序就容易崩,稳定性最差。
- Output Parser:比如
StrOutputParser、JsonOutputParser、PydanticOutputParser。它更接近“模型已经输出了,我再把结果转成程序可用的数据”,适合prompt | model | parser这条经典链路。 - Structured Output:比如
with_structured_output(TypedDict / Pydantic / JSON Schema)。它更接近“在模型输出前就先把 schema 定好”,让模型按结构生成并自动解析,约束力更强。 - Function Calling / Tool Calling:本质上也是结构化输出的一种,只不过目标更偏“生成调用意图和参数”,特别适合工具调用、外部接口编排和 Agent。
如果模型平台支持更严格的结构化输出能力,通常会优先采用只保证“返回合法 JSON”的模式,因为前者更接近“按 schema 生成”,后者很多时候只是“长得像 JSON”。
如果再往下细分:
TypedDict适合字段固定、但不追求很强运行时校验的场景。Pydantic适合字段类型、范围、必填项比较严格的业务场景。JSON Schema更适合跨语言、跨系统共享结构协议。
工程上通常不会把“让模型输出 JSON”当成真正的生产方案,更稳的顺序通常是:
- 能用原生结构化输出就优先用。
- 需要强校验时优先用 Pydantic 一类 schema。
- 即使模型已经结构化输出,也要补业务校验、缺省值处理和失败重试。
归根结底,Prompt 只能“尽量要求它长成这样”,Parser 和 Structured Output 才是在帮你把模型输出真正接进程序系统。
重要度:高频(出现次数:1次) | 难度:中等
考察点:从“能输出 JSON”到“能稳定接系统”的工程意识。
对应章节:14 输出解析器 §1、输出解析器简介;14 输出解析器 §4、结构化输出
Q2-3. Prompt Injection 是什么?为什么 RAG / Agent 场景更要小心?
Prompt Injection 本质上是:不可信输入试图改写系统指令、诱导模型越权,或者让 Agent 执行原本不该执行的动作。
它不只来自用户直接输入,也可能藏在网页、PDF、知识库文档、邮件甚至图片里。只要这些内容会被模型读到,就可能形成“间接注入”。所以在 RAG、浏览器 Agent、代码解释器这类会读外部内容的系统里,风险会明显更高。
通常会从五层做防护:
- 指令分层:System 放稳定规则,User 和外部文档都视作不可信输入,不能和系统约束同权。
- 内容隔离:检索片段、网页内容、用户上传文档只当“待分析材料”,不要默认它们能发新指令。
- 工具权限:所有写操作、危险操作都走白名单、schema 校验和最小权限,不让模型拿裸能力。
- 动作校验:关键输出做规则校验,关键动作做确认、审批或 HITL,不让一句恶意文本直接触发副作用。
- 观测与兜底:记录注入样例、失败轨迹和异常调用,及时加入评测集和拦截规则。
这里有一句特别关键:RAG 不是安全层,知识库文档本身也可能带恶意指令。 只要把“检索到的内容”直接当成“可信提示词”,系统就很危险。
常见追问:
- 如果文档里写着“忽略之前所有指令”,你会怎么处理?
- 浏览器 Agent 怎么防网页里的隐藏提示词?
- 为什么微调和对齐不能从根上解决 Prompt Injection?
重要度:高频(出现次数:1次) | 难度:中等
考察点:应用层安全意识,以及对直接注入、间接注入和工具越权风险的理解。
对应章节:13 提示词与消息模板 §1、Prompt 简介;17 Tools工具调用 §6、从课程案例走向真实项目
Q2-4. Prompt Engineering 中,如何系统优化提示词并把准确率做上去?
通常不会把 Prompt 优化理解成“反复手改几句提示词”,而是把它当成一个小型工程闭环。真正想把准确率稳定做上去,核心不是玄学技巧,而是 任务拆解、样例驱动、评测回归和边界收敛。
比较稳的做法通常是:
- 先定义任务边界:先说清楚什么叫答对,什么叫答错,哪些情况必须拒答或澄清。
- 做输入分层:把稳定规则放
System,动态任务放User,别把所有约束揉成一段长 prompt。 - 先修大问题再修措辞:如果问题本质是缺知识、缺工具、缺流程,不要强行靠 Prompt 硬抬效果。
- 用样例和反例:few-shot 示例、反例约束、输出格式示例,通常比抽象要求更稳定。
- 做结构化约束:能用 schema、parser、structured output 的地方,不要只写“请返回 JSON”。
- 建 badcase 集:把线上错例沉淀成固定评测集,每次改 Prompt 都跑回归,而不是靠体感判断“好像更准了”。
- 做版本和灰度:Prompt、模型版本、温度、输出格式要求一起管理,避免改了一个点却查不清影响。
如果从更务实的面试表达出发,还需要补一句:所谓“准确率 > 95%”一定要绑定具体任务,比如分类、抽取、格式生成、客服问答,它不是一个通用承诺值。真正的工程目标应该是让某个具体任务在固定评测集上稳定达标,而不是追一个脱离场景的数字。
常见追问:
- 如果 Prompt 怎么改都上不去,你怎么判断应该切到 RAG、Tool、Workflow 还是微调?
- badcase 集应该怎么建,才能真的指导迭代?
- 如果线上效果突然下降,你怎么判断是 Prompt 问题、模型版本问题,还是外部知识 / 工具链路问题?
重要度:高频(出现次数:1次) | 难度:中等
考察点:Prompt 优化方法论、评测闭环意识,以及是否知道 Prompt 不是万能修复工具。
对应章节:1-2 提示词工程基础 §2、提示词怎么写;14 输出解析器 §8、实际开发选择
Q2-5. Prompt 工程的边界是什么?为什么不能把一切问题都交给 Prompt?
Prompt 更接近一种“任务表达和约束手段”,但它解决不了知识缺失、复杂状态管理和强执行需求。
比如资料太多时,仅靠 Prompt 塞上下文会出现成本高、噪声多、丢关键信息的问题,这时候应转向 RAG。多步骤复杂流程如果全靠 Prompt 硬编排,会变成不可维护的长指令,应该转 Workflow 或 LangGraph。需要稳定调用外部系统时,应该走 Tools / Function Calling,而不是让模型在文本里“假装调用了接口”。
所以工程上更合理的思路是:Prompt 负责表达规则,RAG 负责补知识,Tools 负责执行能力,Graph / Workflow 负责流程控制。
重要度:中频(出现次数:1次) | 难度:基础
考察点:是否理解 Prompt 不是万能药。
对应章节:1-2 提示词工程基础 §4、提示词工程的边界;13 提示词与消息模板 §1、Prompt 简介
Q2-6. 如何做 Prompt 版本管理与回归?
如果 Prompt 已经进入生产环境,就不能再把它当成一段“临时改改的字符串”,而要把它当成配置资产或代码资产来管理。
通常会同时管理四样东西:
- Prompt 模板本身:System、User 模板、few-shot 示例、输出格式要求。
- 运行参数:模型名、模型版本、temperature、top_p、max_tokens。
- 评测集:固定 golden questions、典型 badcase、失败样例。
- 发布信息:模板版本号、发布时间、负责人、灰度范围、回滚版本。
更稳的做法通常是:
- Prompt 和代码一起版本化,或者放进配置中心统一管理。
- 每次改 Prompt 都跑固定回归集,不只看主观感觉。
- 记录模板 hash、模型版本和关键参数,避免“明明没改 Prompt,效果却变了”时查不清原因。
- 上线前做灰度和抽样人工复核,高风险链路保留快速回滚能力。
还有一个很容易被忽略的点:换底层模型后,旧 Prompt 不一定还能保持原效果。 所以很多时候回归的对象不是“这段 Prompt 对不对”,而是“Prompt 和当前模型组合起来还稳不稳”。
常见追问:
- golden set 应该怎么构建?
- 如果线上效果波动,你怎么判断是 Prompt 问题还是模型版本问题?
- Prompt、模型、索引三者一起变更时,如何设计回滚策略?
重要度:中频(出现次数:1次) | 难度:中等
考察点:Prompt 工程化治理能力,而不是只会手改提示词。
对应章节:13 提示词与消息模板 §8、从文件加载提示词;1-2 提示词工程基础 §5、提示词工程的几个注意点
3、RAG 全链路
Q3-1. 请描述一个完整的 RAG 流水线。
完整 RAG 一般分索引阶段和检索阶段。
索引阶段是:文档加载、清洗、切块、向量化、写入向量库,并补充元数据。检索阶段是:接收问题、必要时做 query 改写、检索召回、重排过滤、上下文组装、交给模型生成答案,最后再做引用、脱敏和格式化输出。
工程上真正难的通常不在“把模型接起来”,而在三件事:文档是否被正确解析,切块是否保留语义,检索结果是否真的对当前问题有帮助。线上效果差,大多数时候也是卡在这三层,而不是卡在最后那一轮生成。
常见追问:
- chunk 应该怎么切?
- metadata 应该存哪些字段?
- 为什么要加 rerank?
重要度:必刷(出现次数:3次) | 难度:中等
考察点:是否真正理解 RAG,而不是只会说“检索增强生成”。
对应章节:19 RAG检索增强生成 §1、RAG 简介;19 RAG检索增强生成 §2、RAG 文本处理核心知识
Q3-2. 文本切块应该怎么设计?有哪些常见坑?
切块的目标不是“切得均匀”,而是“尽量保留对检索有意义的语义单元”。
通常会结合固定长度、段落边界、标题层级和重叠窗口来设计。太大,会导致噪声多、命中不准;太小,会丢上下文,检索回来也无法回答。对于手册、FAQ、制度文档、图文混排 PDF,这些文档往往不能只按字符数切,还要结合版面结构、标题、表格和语义边界。
常见坑包括:切块过碎、忽略标题层级、重叠不足、纯 OCR 文本脏乱不清洗、表格内容被打散,以及把多个主题塞进一个 chunk 导致向量语义污染。
重要度:高频(出现次数:3次) | 难度:中等
考察点:RAG 基础功是否扎实。
对应章节:19 RAG检索增强生成 §2、RAG 文本处理核心知识;2-RAG 搭建企业私有&个人知识库 §2、知识库的概述
Q3-3. RAG 效果不好时,你会怎么排查?
排查时可按“检索前、检索中、检索后”三层展开。
- 检索前:看 query 是否表达清楚,是否需要改写、拆问、补充上下文。
- 检索中:看切块、Embedding、向量库索引参数、top_k、过滤条件、重排是否合理。
- 检索后:看拼给模型的上下文是否过长、是否有噪声、是否出现关键信息被淹没、生成提示是否要求“无依据就拒答”。
如果是企业知识库,还要额外查文档质量、版本是否最新、权限过滤是否误伤。很多所谓“模型不聪明”的问题,最后都能定位到文档解析脏、chunk 太碎、召回噪声大或者上下文组装差。
常见追问:
- top_k 是不是越大越好?
- 如何识别是召回问题还是生成问题?
- 什么是 Lost in the Middle?
重要度:必刷(出现次数:1次) | 难度:中等
考察点:排障能力、定位思路是否分层。
对应章节:19 RAG检索增强生成 §2、RAG 文本处理核心知识;19 RAG检索增强生成 §3、RAG 综合案例:智能运维助手
Q3-4. 混合检索、重排序和查询改写分别解决什么问题?
这三者解决的是三个不同层次的问题。
- 查询改写解决“用户问题表达得不够像检索语句”的问题。
- 混合检索解决“只靠稠密向量可能漏掉关键词命中”的问题。
- 重排序解决“召回了一批候选,但顺序还不够准”的问题。
真实项目里,尤其是制度、产品参数、订单字段、错误码这类知识,关键词常常非常重要,所以仅靠 dense retrieval 往往不够。比较稳的方案通常是“query 改写 + 稠密检索 + 稀疏检索 + rerank”,再结合 metadata 过滤做收口。
常见追问:
- 稠密检索和稀疏检索的区别是什么?
- Rerank 放在哪一层最合适?
- 混合检索会不会更贵?
重要度:高频(出现次数:1次) | 难度:中等
考察点:是否理解检索优化的职责分工。
对应章节:18 向量数据库与Embedding实战 §6、向量库的写入与检索;19 RAG检索增强生成 §2、RAG 文本处理核心知识
Q3-5. RAG 应该如何接入 Agent 执行链路?
RAG 接到 Agent 里,不能只理解成“先查一遍资料再回答”,更稳的做法通常是把检索能力做成 Agent 可按需调用的一环。
常见有三种接法:
- 前置检索:用户问题一进来先统一检索,再把证据交给 Agent 继续规划。
- 按需检索:把 Retriever / Search 封成工具,Agent 在需要事实依据时主动调用。
- 后置校验:先生成结果,再用检索做证据核对或引用补全。
真实项目里,通常更推荐“按需检索为主,前置检索为辅”。因为不是每一步都需要查知识库,有些步骤是规划、路由、参数确认或工具执行。把 RAG 做成可调用能力,能减少无效检索,也更适合多步任务。后置校验可以作为增强,但通常不适合作为主链路,否则会增加延迟,而且一旦前面已经跑偏,后面再校验修正成本会更高。
常见追问:
- 你了解生成过程中多次检索、自适应检索或 Agentic RAG 吗?它和“先检索一次再生成”有什么区别?
- 什么情况下应该从“一次检索”升级成“多轮检索 + 中途补检”?
重要度:高频(出现次数:1次) | 难度:中等
考察点:是否理解 Agentic RAG 的接入方式,而不是只会把 RAG 当成固定前置流水线。
对应章节:19 RAG检索增强生成 §1、RAG 简介;21 Agent智能体 §6、小结:Agent、Tool、Function Calling、RAG、MCP 的区别与联系
Q3-6. 为什么企业项目里的 RAG 必须重视权限、版本和引用?
因为企业知识不是“能查到就行”,而是“要查对、查对权限范围、还能追溯来源”。
权限上,向量检索必须做租户隔离、角色过滤、文档级或段落级权限控制,不能把安全寄希望于模型“自觉不回答”。版本上,索引和源文档要可追踪,文档更新后要知道哪些向量需要增量重建。引用上,如果答案不能回溯到原文片段,业务方很难建立信任,也不利于审计和复盘。
所以在企业里,RAG 不只是检索效果问题,更是数据治理问题。
重要度:中频(出现次数:1次) | 难度:中等
考察点:企业落地意识,不只停留在 Demo。
对应章节:2-RAG 搭建企业私有&个人知识库 §2、知识库的概述;19 RAG检索增强生成 §3、RAG 综合案例:智能运维助手
Q3-7. 什么是 Lost in the Middle?它对 RAG 设计有什么启示?
Lost in the Middle 指的是:上下文一长,模型往往更容易利用开头和结尾的信息,而把中间那部分关键证据“看见了但没用好”。
它对 RAG 的启示非常直接:不是把更多 chunk 塞进上下文就一定更好。如果中间噪声太多,真正关键的证据反而更容易被淹没。
因此,工程上通常采用以下处理方式:
- 先做 rerank 和过滤,再决定谁能进上下文。
- 尽量减少低价值片段,别把“可能相关”都塞进去。
- 重要证据优先放前面,必要时做摘要或结论前置。
- 对复杂问题采用分阶段检索,而不是一次性把大段材料全塞进去。
总结来说:长上下文不等于高质量上下文,RAG 的核心仍然是“把最有用的证据放到最容易被模型用到的位置”。
重要度:中频(出现次数:1次) | 难度:中等
考察点:长上下文理解能力,以及检索、重排、上下文组装之间的联动意识。
对应章节:19 RAG检索增强生成 §2、RAG 文本处理核心知识;19 RAG检索增强生成 §3、RAG 综合案例:智能运维助手
Q3-8. 有了超长上下文模型,还需要 RAG 吗?
多数场景下仍然需要,因为“能塞进去”和“应该塞进去”完全不是一回事。
超长上下文模型确实让长文理解、少量文档内推理变得更方便,但企业场景里更常见的问题不是“模型窗口不够大”,而是:
- 知识总量太大,没法每次都全塞。
- 长上下文成本和延迟都更高。
- 权限过滤、租户隔离、版本控制不能只靠窗口硬塞解决。
- 审计和引用追溯更适合通过检索链路来做。
所以更合理的理解通常是:长上下文扩大了可处理范围,但没有消灭检索的价值。 它更适合少量长文档分析、上下文整合和复杂推理;RAG 更适合海量知识缩圈、权限过滤、证据引用和持续更新。
如果压缩成一句话,可以概括为:长上下文不是 RAG 的替代品,更接近 RAG 的补充能力。
重要度:中频(出现次数:1次) | 难度:中等
考察点:是否理解“长窗口能力”和“检索系统价值”不是同一层问题。
对应章节:19 RAG检索增强生成 §1、RAG 简介;1-1 大模型认知与工程概览 §1、认识大模型
Q3-9. 复杂文档 RAG 或多模态 RAG 应该怎么做?
这类场景最大的坑,是把 PDF、扫描件、表格、图片、网页全都当成“普通纯文本”来处理。
更稳的做法通常是先把解析层单独看待:
- 先做版面分析、OCR、表格提取、标题层级恢复,而不是一开始就按字符切块。
- chunk 尽量按结构块切,比如标题、段落、表格、图注,而不是把一张表切成几段残缺文本。
- metadata 要保留页码、章节标题、文档版本、来源文件、坐标或结构位置信息,方便引用和追溯。
- 查询时必要时走多路召回,例如文本召回、表格召回、图片说明召回,再统一重排和组装。
如果是企业知识库,复杂文档 RAG 往往比“模型选型”更影响上限。因为解析错了、表格碎了、图文对应关系丢了,后面再强的模型也很难补回来。
常见追问:
- 为什么很多设备手册、制度 PDF 特别难做 RAG?
- OCR 准确率和最终问答效果是什么关系?
- 多模态 RAG 什么时候值得上,什么时候纯文本就够?
重要度:中频(出现次数:1次) | 难度:较难
考察点:是否意识到 RAG 不只是检索问题,还包括文档解析、结构恢复和引用追踪。
对应章节:19 RAG检索增强生成 §2、RAG 文本处理核心知识;2-RAG 搭建企业私有&个人知识库 §2、知识库的概述
Q3-10. 什么是 Agentic RAG?它和“一次检索后直接生成”有什么区别?
传统 RAG 更接近固定流水线:先检索一次,再把结果交给模型生成答案;Agentic RAG 则更接近一个闭环过程,模型或 Agent 会根据当前进展决定要不要检索、检索什么、是否改写 query、是否继续补检,甚至在发现证据不足时主动回头重查。
两者最大的区别,不在于“有没有检索”,而在于检索是不是被当成一个动态决策过程来管理。
- 传统 RAG:流程稳定、成本更可控、实现简单,适合单轮问答和大多数标准知识库场景。
- Agentic RAG:更适合复杂问题、多跳证据、需要自检补检的场景,但代价是链路更长、成本更高、失控风险也更高。
所以工程上通常不会一开始就上 Agentic RAG,而是先看 badcase 是否真的集中在这些问题上:
- 一次检索经常拿不到足够证据。
- 问题本身需要拆问、多轮求证或多源交叉验证。
- 生成前需要先判断“到底该查知识库、查网页,还是查业务系统”。
如果真的要落地 Agentic RAG,我一定会同时补上步数上限、预算限制、失败退出条件和可观测性,否则它很容易从“更聪明的检索”变成“更贵的随机游走”。
常见追问:
- 什么情况下应该坚持固定 RAG,而不是升级成 Agentic RAG?
- Agentic RAG 的收益通常体现在哪些 badcase 上?
- 如何防止多轮补检把延迟和成本拉得太高?
重要度:中频(出现次数:1次) | 难度:较难
考察点:是否真正理解 RAG 从固定流水线向智能检索闭环演进的边界和代价。
对应章节:19 RAG检索增强生成 §1、RAG 简介;21 Agent智能体 §1、Agent 简介
4、检索基础设施与检索优化
Q4-1. 什么是向量数据库?它和传统数据库有什么区别?为什么不能只用 MySQL?
先下一个定义:向量数据库是一种专门面向“相似度检索”的存储系统,用来保存向量、原始内容和元数据,并支持近邻搜索、过滤和召回。
传统数据库擅长精确过滤、事务和结构化查询,而向量数据库擅长按“语义相似度”做近邻检索。两者不是替代关系,而是分工关系。
如果只用 MySQL,你当然也能存 Embedding,但一旦数据量上来,近似向量检索、ANN 索引、混合检索和大规模召回都会变得低效,而且工程复杂度会明显上升。企业里更常见的做法是:结构化条件走传统数据库,语义召回走向量库,再通过 metadata 把两边结合起来。
重要度:高频(出现次数:2次) | 难度:基础
考察点:底层检索设施理解。
对应章节:18 向量数据库与Embedding实战 §2、向量数据库;18 向量数据库与Embedding实战 §6、向量库的写入与检索
Q4-2. Embedding 是什么?它在应用开发里最核心的价值是什么?
Embedding 的本质,是把文本、图片这类原始内容变成一串固定长度的向量,让“语义关系”尽量映射成“空间关系”。换一种工程化表述,就是把“意思接近”变成“距离接近”,这样程序才能做相似度计算。
它在应用开发里最核心的价值,不只是“把文本编码成数字”,而是让系统第一次具备了按语义检索的能力。所以它常被拿来做语义搜索、相似匹配、去重、聚类、推荐和 RAG 召回。
放到 RAG 里,Embedding 决定的是“问题和知识能不能在向量空间里正确相遇”。如果 Embedding 选错了,后面的向量库、重排、Prompt 再怎么补,也只能在一个偏掉的召回基础上做优化。
另外有两个工程细节需要特别强调:
- 建库和查询最好用同一套 Embedding 模型,不然要么维度对不上,要么即使维度一样,相似度也可能没有意义。
- Embedding 只是把文本变成“稠密向量”这一层,真正检索时往往还会和标量字段过滤、甚至稀疏检索一起配合,不能把它理解成检索系统的全部。
重要度:高频(出现次数:1次) | 难度:基础
考察点:是否理解 Embedding 不是“附属模型”,而是检索基础设施。
对应章节:18 向量数据库与Embedding实战 §1、向量与向量化;18 向量数据库与Embedding实战 §4、Embedding 文本向量化
Q4-3. 如何选择一个合适的 Embedding 模型?评估它好坏看什么?
通常不会先看榜单,而是先看自己的知识类型和检索目标,再反推模型选型。更合理的思路是先看三件事:
- 语言和领域适配:中文、英文、多语言、代码、法律、金融,适合的模型可能完全不同。
- 建库和查询一致性:建库和查询尽量使用同一套 Embedding 模型,这是底线。
- 成本和延迟是否可接受:不是维度越高越好,也不是模型越大越好,要看知识库规模和查询量能不能撑住。
真正评估时,更应关注“它有没有把整条检索链路整体抬起来”,而不是只看一个榜单分数。比较实用的判断通常有三层:
- 检索层:相关片段有没有更容易被召回,坏例子有没有减少,重排前后的差距大不大。
- 业务层:最终答案的忠实度、引用准确性、任务完成率有没有变好。
- 工程层:延迟、成本、接入复杂度能不能接受。
工程上需要特别强调两点:
- Embedding 选型不是孤立问题,它会和切块策略、索引参数、混合检索、rerank 一起决定上限。
- 公开榜单只能做初筛,真正能决定要不要上线的,还是你自己的知识样本和 badcase。
所以更稳的做法通常是:先选两到三套候选模型跑通,再在自己的知识库样本上做小规模对比,观察召回质量、最终回答质量和成本延迟,最后再决定。
重要度:高频(出现次数:1次) | 难度:中等
考察点:Embedding 选型、评测意识,以及“模型一致性 + 检索链路联动”的理解。
对应章节:18 向量数据库与Embedding实战 §4、Embedding 文本向量化;18 向量数据库与Embedding实战 §6、向量库的写入与检索
Q4-4. 近似检索和精确检索怎么权衡?
精确检索效果更可控,但在大规模数据下成本高、延迟高。近似检索牺牲一部分精确性,换来更好的吞吐和响应速度,所以大部分线上系统都会优先用近似检索。
工程上不是简单二选一,而是看业务要求。如果是高风险检索、数据量不大、对结果一致性要求极高,可以用精确或更保守的索引策略。如果是通用知识问答、商品搜索、海量文档召回,近似检索通常更现实,再配合 rerank 做补偿。
重要度:中频(出现次数:1次) | 难度:中等
考察点:性能和效果取舍能力。
对应章节:18 向量数据库与Embedding实战 §2、向量数据库;18 向量数据库与Embedding实战 §5、通过向量计算语义相似度
Q4-5. 在什么场景下,知识图谱或图数据库比纯向量检索更有价值?
当问题的核心不只是“语义相似”,而是“实体关系、路径推理、多跳关联、结构化约束”时,知识图谱或图数据库会更有价值。
比如设备故障排查、商品属性关联、组织关系、法规条文引用、依赖链分析,这些场景往往更依赖“谁和谁有什么关系”,而不只是“哪段文本看起来最像”。这时候如果只靠向量检索,容易召回到语义接近但关系错误的内容。
工程上更适合把它理解成增强,而不是简单替代。比较稳的做法通常是:向量检索负责语义召回,知识图谱负责结构化约束、实体消歧和关系推理。只有在问题本身高度结构化、关系链特别关键时,图数据库才可能成为主通路。
重要度:中频(出现次数:1次) | 难度:较难
考察点:对“语义召回”和“关系推理”边界的理解,以及检索架构选型能力。
Q4-6. GraphRAG 适合什么场景?和传统向量 RAG 怎么选?
GraphRAG 更适合实体关系密、需要多跳推理、社区摘要或结构化约束很强的场景。
比如组织关系分析、供应链依赖、设备故障链路、法规条款关联、人物事件网络,这些问题往往不是“哪段文字最像”,而是“谁和谁怎么关联、要经过几跳才能到答案”。这时候图谱或图数据库的优势会更明显。
但工程上通常不会默认一开始就做 GraphRAG。更常见的稳妥路径是:
- 先用传统向量 RAG 打底,解决大多数语义召回问题。
- 当 badcase 明显集中在多跳关系、实体消歧、结构约束时,再引入图谱增强。
- 真正落地时,也常常不是二选一,而是“向量召回 + 图谱召回 + 融合重排”一起做。
更准确的结论是:只有当问题核心在关系结构上时,图谱路线的收益才会大于复杂度。
常见追问:
- GraphRAG 和知识图谱数据库是什么关系?
- 什么情况下多路召回比纯图谱主路更现实?
- 图谱更新和维护成本会不会太高?
重要度:中频(出现次数:1次) | 难度:较难
考察点:GraphRAG 选型能力,以及对复杂度和收益的权衡意识。
5、Tools、Function Calling 与 Agent
Q5-1. ReAct 模式是什么?它解决了什么问题?
ReAct 的价值在于把“推理”和“行动”串起来,让模型不只是想,还能基于外部观察不断调整下一步。
它通常表现为 Thought -> Action -> Observation 的循环。模型先做当前判断,再决定调用什么工具,然后根据工具返回的结果更新判断,直到任务完成。相比只靠一次性生成,ReAct 更适合不确定路径、多步决策和外部信息依赖强的任务。
这里还需要补充一个重要点:现代 Tool Calling Agent 不一定会把 Thought 明文展示出来。实际代码中更常见的是 AIMessage.tool_calls、工具执行结果和下一轮消息。因此,更准确的理解方式是:ReAct 更接近一种工作机制,而不只是某个固定 Prompt 模板。
工程上要注意,ReAct 的优势是灵活,但代价是链路更长、成本更高、调试更复杂,所以并不是所有场景都值得上完整循环。
常见追问:
- Chain of Thought(CoT)是什么?它为什么能提升复杂推理题的效果?
- CoT 和 ReAct 的区别是什么?什么时候只用 CoT 就够了,什么时候要升级到 ReAct?
重要度:高频(出现次数:3次) | 难度:基础
考察点:Agent 运行范式理解。
对应章节:21 Agent智能体 §3、Agent 工作原理(V0.3);17 Tools工具调用 §2、工具调用的工作方式
Q5-2. Tool、Function Calling、Agent 三者是什么关系?
Tool 是能力单元,Function Calling 是调用机制,Agent 是把模型、工具、状态和决策流程组织起来的运行模式。
Tool 解决“能做什么”,Function Calling 解决“怎么发起结构化调用”,Agent 解决“如何围绕目标多轮地决定要不要调用、调用谁、调用几次”。所以 Tool 不等于 Agent,单次 Tool Calling 也不一定等于 Agent。
在真实项目里,很多需求只需要 Tool Calling,不一定要上完整 Agent。只有当任务需要多步规划、反复决策和状态推进时,Agent 才值得引入。
常见追问:
- 单工具助手算不算 Agent?
- ReAct 和 Function Calling 什么关系?
- Tool 设计的最小粒度应该是什么?
重要度:必刷(出现次数:2次) | 难度:基础
考察点:概念拆分是否清楚。
对应章节:17 Tools工具调用 §1、Tools 简介;21 Agent智能体 §6、小结:Agent、Tool、Function Calling、RAG、MCP 的区别与联系
Q5-3. 一个 Agent 应该如何设计?它通常由哪些核心组件构成?
如果按最小理解版概括,可以写成:Agent = 模型 + 工具集 + 运行循环 + 当前状态。
- 模型:负责理解目标、分析当前局面、决定下一步。
- 工具集:负责提供外部能力,比如搜索、数据库、文件系统、业务 API。
- 运行循环:负责让系统不是只回答一次,而是能“想一步、做一步、看结果、再决定下一步”。
- 当前状态:负责保存消息、工具返回、中间结果和线程上下文。
在这个最小骨架之上,记忆、规划、反思、多智能体协作都可以继续往上叠,但它们更适合作为“扩展能力”来理解,而不是每个 Agent 一上来都必须全部具备。
如果从工程设计看,一个可落地的 Agent,重点应控制五件事:
- 目标和职责:一个 Agent 解决什么问题,要尽量单一,不要什么都做。
- 工具边界:只暴露完成任务真正需要的能力,避免给模型一把通吃的权限。
- 状态设计:当前任务进展、中间结论、消息历史放在哪里,要显式设计。
- 停止条件:什么时候继续、什么时候澄清、什么时候转人工、什么时候结束,不能模糊。
- 治理能力:日志、Trace、重试、超时、审计、人工接管都要补齐。
概括来说:Agent 设计的重点不是“堆更多能力”,而是围绕 模型、工具、循环、状态 四条主线,把系统做得更可控、更可追踪、更能稳定交付结果。
常见追问:
- 在构建一个复杂 Agent 时,你认为最主要的挑战是什么?
- 为什么很多 Agent 不是能力不够,而是状态、边界和治理没设计好?
重要度:必刷(出现次数:2次) | 难度:中等
考察点:Agent 架构能力,而不是停留在概念层。
对应章节:21 Agent智能体 §1、Agent 简介;21 Agent智能体 §6、小结:Agent、Tool、Function Calling、RAG、MCP 的区别与联系
Q5-4. Function Calling 的基本原理是什么?
Function Calling 的核心分工,可以概括成一句话:模型负责决策,程序负责执行。
更完整地说,这条链路通常是:
- 宿主程序把工具的
name、description、参数 schema 一起发给模型。 - 模型判断要不要调用工具,如果要,会先返回结构化的
tool_calls,而不是直接给最终自然语言。 - 宿主程序读取
AIMessage.tool_calls,真正去执行 Python 函数、HTTP API、数据库查询或业务服务。 - 程序把执行结果封成
ToolMessage或等价消息,再交回模型。 - 模型基于工具结果生成最终答复。
所以 Function Calling 不是模型自己去调 API,它只是生成“调用哪个工具、传什么参数”的结构化意图。真正的鉴权、参数校验、超时、幂等、重试和安全控制,仍然都在宿主程序这一层。
如果要答得更工程化,还需要补一句:Function Calling 解决的是“模型如何表达调用意图”,不解决“工具是否安全、是否允许调用、调用失败怎么兜底”这些问题。
常见追问:
- Function Calling 和文本解析调用有什么区别?
- 为什么生产环境更推荐原生 Tool Calling?
- 工具执行失败怎么处理?
重要度:必刷(出现次数:1次) | 难度:中等
考察点:是否理解模型和宿主程序的职责边界。
对应章节:17 Tools工具调用 §1、Tools 简介;17 Tools工具调用 §2、工具调用的工作方式
来源:腾讯AI后台工程师一面(实际大厂面试题)
Q5-5. 设计 Tool 时最值得重视的工程原则是什么?
最值得重视的四点是:单一职责、明确 schema、可观测、可失败。
单一职责意味着一个 Tool 只做一件清晰的事,避免“大而全万能工具”。明确 schema 是为了让模型更稳定地理解参数含义。可观测意味着每次调用都要能追踪请求、参数、耗时、结果和错误。可失败意味着工具必须有超时、异常码、重试策略和降级逻辑,而不是假设一定成功。
如果 Tool 要连业务系统,还要加权限校验、幂等控制和审计日志。因为工具一旦从“查天气”变成“发消息、下工单、写数据库”,风险等级就完全不同了。
重要度:高频(出现次数:1次) | 难度:中等
考察点:是否有把工具接到生产系统的经验意识。
对应章节:17 Tools工具调用 §3、自定义 Tool;17 Tools工具调用 §4、参数 schema:为什么要配合 Pydantic
Q5-6. 如果让你实现一个“查天气、查新闻”的 AI 助手,你会怎么设计?
这类题在面试里更推荐先做场景判断,再谈方案。像查天气、查新闻这种需求,核心不是复杂规划,而是“意图识别 + 工具调用 + 结果整合”。
一个比较稳的实现通常包括四层:
- 输入与路由层:先判断用户是查天气、查新闻,还是两者都要。必要时抽取城市、时间、新闻主题等参数。
- 工具层:把天气查询和新闻检索分别封成清晰的 Tool,定义好 schema、参数说明、异常返回和超时策略。
- 调度层:先用 Function Calling 或轻量 Agent,让模型决定调用哪个工具、怎么传参、要不要串联多个工具。
- 输出层:把工具结果做统一整理,比如天气给关键字段,新闻给摘要和来源,最后再由模型生成自然语言回复。
如果需求只是单轮查天气、查新闻, Prompt + Function Calling 往往就够了,不一定要上完整 ReAct Agent。只有在需求升级成“先查天气,再根据天气推荐本地新闻,再继续追问某条新闻细节”这种多步动态链路时,Agent 的价值才会更明显。
工程上需要特别关注三点:工具描述要清楚,避免模型乱调;外部 API 要有降级和超时;结果输出最好带来源或关键字段,避免模型把工具结果又二次编造一遍。
重要度:高频(出现次数:1次) | 难度:中等
考察点:能否把一个简单助手拆成“意图、工具、调度、输出”四层,而不是一股脑说上 Agent。
对应章节:17 Tools工具调用 §5、天气助手实战;21 Agent智能体 §5、实操与案例
来源:腾讯AI后台工程师一面(实际大厂面试题)
Q5-7. 什么是“工具幻觉”?工程上怎么缓解?
工具幻觉指的是:模型调用了不存在的工具、传了不合理的参数,或者在只该读数据时却试图触发写操作。
这类问题在工具数量多、描述模糊、schema 不清晰时特别常见。模型有时不是“不会用工具”,而是“对工具边界理解错了”。
工程上通常会这样兜底:
- 工具注册表白名单:不存在的工具名直接拒绝。
- schema 强校验:参数类型、必填项、枚举值都要在运行时校验,不信任模型返回。
- 工具集收敛:按场景暴露最小工具集,别让模型在几十个相近工具里乱猜。
- 描述清晰:工具名、description、参数含义要能明显区分读写边界和适用场景。
- 高风险确认:涉及发消息、写数据库、发工单、下单等副作用动作,必须审批或二次确认。
- 失败反馈:把错误原因回传给模型,让它有机会修正,但要限制重试次数,避免死循环。
可以概括为:Tool Calling 不是“接上就行”,而是要让模型“调得准、调得住、调错了也出不了大事”。
重要度:高频(出现次数:1次) | 难度:中等
考察点:对 Tool Use 稳定性和风险控制的理解。
对应章节:17 Tools工具调用 §6、从课程案例走向真实项目;17 Tools工具调用 §4、参数 schema:为什么要配合 Pydantic
Q5-8. Function Calling 和“让模型输出 JSON 再自己解析”有什么区别?
两者表面上都像“模型输出结构化结果”,但稳定性和工程边界差别很大。
- 让模型输出 JSON,更接近在 Prompt 层提出要求。它简单、通用、起步快,但模型一旦多说一句自然语言、少一个字段、类型写错,程序端就要自己兜底。
- Function Calling 更接近协议级结构化输出。模型不是随便返回一段文本,而是明确返回工具名和参数,宿主按约定解析和执行,可靠性通常更高。
因此,在生产环境中通常这样判断:
- 如果只是抽取字段、做轻量结构化返回,而且平台原生能力有限,先用 JSON + parser。
- 如果要进入真实工具调用、外部系统编排、副作用执行,优先用原生 Function Calling 或等价 Tool Use 协议。
更关键的区别还在于职责边界。JSON 解析更接近“你让模型尽量长成这个格式”;Function Calling 更接近“模型和宿主之间有一层明确契约”。这层契约越清楚,后面的校验、审计、重试和观测就越好做。
常见追问:
- 什么时候 JSON + parser 已经够用?
- 为什么 Function Calling 更适合接生产系统?
- 如果平台没有原生 Function Calling,怎么尽量把 JSON 方案做稳?
重要度:中频(出现次数:1次) | 难度:中等
考察点:结构化输出和工具调用之间的协议级差异理解。
对应章节:14 输出解析器 §4、结构化输出;17 Tools工具调用 §2、工具调用的工作方式
Q5-9. 当工具很多时,如何控制上下文长度和调用稳定性?
工具一多,问题通常不再是“会不会调工具”,而是“会不会在一堆相似工具里选错、调错、调得越来越乱”。
比较稳的做法通常有四类:
- 分层暴露:先按业务域拆成搜索类、数据类、执行类、写操作类,不要一次把所有工具都暴露给同一个 Agent。
- 动态选择:先做 Tool Retrieval 或路由,只把 Top-N 个最相关工具描述放进当前上下文,而不是全量列出来。
- 工具聚合:把过细的底层 API 封装成更高层的业务动作,减少模型在几十个相近函数里猜名字。
- 描述治理:工具名、description、参数说明里要明确“什么时候用、什么时候不用、有没有副作用”。
如果再往前走一步,生产里还可以把“工具选择”本身做成一个独立阶段,比如先做意图分类或子域路由,再进入具体工具调用。这样既能控上下文长度,也能降低工具幻觉和误调用概率。
可以概括为:工具多不是问题,关键是别让模型在一个没有层次的工具海里盲选。
常见追问:
- 工具太多时,应该优先做路由还是优先改工具粒度?
- Tool Retrieval 和普通知识检索有什么区别?
- 为什么业务动作级工具通常比原子 API 更适合模型调用?
重要度:中频(出现次数:1次) | 难度:较难
考察点:多工具场景下的上下文治理和工具编排能力。
对应章节:17 Tools工具调用 §6、从课程案例走向真实项目;21 Agent智能体 §5、实操与案例
6、Agent 框架与编排:LangChain、LCEL、LangGraph
Q6-1. LangGraph 相比普通 Workflow 或链式调用,最大的价值是什么?
LangGraph 最大的价值是把复杂流程中的状态、节点、边和循环显式表达出来,让大模型驱动的动态流程真正可设计、可追踪、可恢复。
普通链式调用适合简单串行流程,Workflow 适合固定路径,而 LangGraph 更适合有分支、循环、人工中断、状态持久化和多 Agent 协作的任务。它不是“换一种写法”,而是把复杂控制流从隐式逻辑变成显式图结构。
所以当你发现系统开始出现“要不要继续查、什么时候结束、失败后从哪恢复、状态怎么跨轮保留”这些问题时,LangGraph 的优势就出来了。
常见追问:
- LangGraph 和 Agent 的关系是什么?
- 什么场景下普通 Workflow 就够了?
- 图编排的维护成本会不会更高?
重要度:必刷(出现次数:1次) | 难度:中等
考察点:是否理解图编排的必要性。
对应章节:22 LangGraph概述与快速入门 §1、LangGraph 简介;23 LangGraphAPI:图与状态 §1、Graph API 之 Graph
Q6-2. 在 LangGraph 里,State、Node、Edge 分别代表什么?
State 是共享状态,Node 是处理逻辑,Edge 是流转规则。
State 决定图里“大家共同操作的上下文”是什么;Node 决定每一步具体做什么,比如调用模型、查库、执行工具;Edge 决定执行完当前节点后下一步去哪里,可以是固定跳转,也可以是条件路由。
真实项目里,LangGraph 设计得好不好,往往取决于 State 是否清晰、Node 粒度是否合理、Edge 条件是否可解释。很多图写得难维护,不是因为图本身复杂,而是把太多隐式逻辑塞进了一个节点。
重要度:高频(出现次数:1次) | 难度:基础
考察点:LangGraph 基础心智模型。
对应章节:23 LangGraphAPI:图与状态 §2、Graph API 之 State;24 LangGraphAPI:节点、边与进阶 §2、Graph API 之 Edge
Q6-3. LangChain 在今天的价值是什么?为什么不是直接手写 SDK 就够了?
LangChain 的价值不在于“能不能调模型”,而在于它把模型输入输出、Prompt、Parser、Retriever、Tools、Memory、Callbacks 等常见能力统一成了一套可组合接口。
如果只是做一个最小 Demo,直接手写 SDK 当然可以。但一旦进入多模型接入、链式编排、结构化输出、可观测和长期维护阶段,统一抽象会明显降低代码分散度。它真正解决的是“应用层编排和生态整合”问题,而不是“替你发一个 HTTP 请求”。
重要度:高频(出现次数:1次) | 难度:中等
考察点:框架选型判断,而不是盲目崇拜框架。
对应章节:9 LangChain概述与架构 §2、LangChain 定位;9 LangChain概述与架构 §4、LangChain 核心模块
Q6-4. 如何使用 LangChain 开发一个 Agent?
一个最小可用的 LangChain Agent,通常按两条路线去讲。
- classic 路线:模型、工具、Prompt、
create_tool_calling_agent(...)、AgentExecutor(...)一层层组起来,更适合理解内部结构。 - 1.x 路线:直接用
create_agent(...),背后由基于 LangGraph 的运行时去驱动循环、状态和工具调用,更接近当前官方主线。
不管走哪条路线,底层都还是同一条主线:
- 先定义 Tools,把外部能力封成清晰的能力单元。
- 再选择 LLM,并让模型知道可调用的工具 schema。
- 然后设计 Prompt,明确角色、工具使用规则、停止条件和输出边界。
- 最后让系统形成“模型决策 -> 工具执行 -> 结果回传 -> 继续或结束”的闭环。
如果是简单场景,可以直接走高层封装;如果是复杂场景,仍然要能讲清楚底层运行主线。面试里真正加分的不是背 API 名,而是能说清 Agent 为什么会调用工具、什么时候停、失败了怎么兜底。
重要度:高频(出现次数:1次) | 难度:中等
考察点:是否理解 Agent 不是“调一个 create_agent 就结束”,而是清楚底层组成。
对应章节:21 Agent智能体 §5、实操与案例;17 Tools工具调用 §5、天气助手实战
Q6-5. LCEL 的价值是什么?为什么说它不只是语法糖?
LCEL 的核心价值是把 Prompt、Model、Parser、Retriever、函数逻辑都抽象成 Runnable,然后通过统一方式组合和调用。
它不只是写法更短,而是让顺序链、分支链、并行链、函数链能够用同一种接口去拼装、调试和替换。这对真实项目很有价值,因为你后面做日志、流式、批处理、回调和局部替换时,统一接口会让系统更可维护。
重要度:中频(出现次数:1次) | 难度:中等
考察点:是否理解链式编排的统一接口思想。
对应章节:15 LCEL与链式调用 §1、Runnable 与统一调用方式;15 LCEL与链式调用 §2、LCEL 简介
Q6-6. LangGraph 的进阶特性里,你觉得最有工程价值的是哪些?
优先关注四类能力:流式处理、持久化、时间回溯、子图。这四类也刚好是 LangGraph 高级特性的主线。
先看流式。LangGraph 的流式不只是“模型 token 一点点吐出来”,更重要的是它能把整张图执行过程流出来。实际项目里常用的 stream_mode 有这些:
values:看每一步结束后的完整状态快照。updates:看当前这一步到底改了哪些字段。messages:看模型消息片段或 token 流。custom:在节点里主动推送业务进度,比如“正在检索知识库”。
再看持久化。LangGraph 里的 checkpointer 更接近线程内、会话内的短期记忆,适合按 thread_id 保存图状态,用于断点恢复、人机协同和多轮任务承接;如果要跨线程、跨会话保存更长期的信息,则更接近 Store 这条线。
时间回溯的工程价值主要在排障、复盘和重放。很多复杂 Agent 不是最后结果错了,而是中间某一步状态被污染了,Time Travel 能帮你从具体节点回看甚至重跑。
子图的价值则是把复杂系统拆成可复用模块。比如“检索子图”“审批子图”“报告生成子图”可以独立封装,主图只负责更高层编排。
这四类能力可以概括为一句话:流式解决可观察,持久化解决可恢复,时间回溯解决可复盘,子图解决可维护。四个一起上,LangGraph 才真正像生产基础设施,而不只是一个能跑通的 Demo 图。
常见追问:
- Checkpointer 和长期记忆有什么区别?
- Time-Travel 适合什么场景?
- 什么时候应该拆子图?
重要度:中频(出现次数:1次) | 难度:较难
考察点:是否关注真实生产能力,而不是只会 HelloWorld。
对应章节:25 LangGraph高级特性 §1、流式处理(Streaming);25 LangGraph高级特性 §2、状态持久化(Persistence);25 LangGraph高级特性 §3、时间回溯(Time-Travel);25 LangGraph高级特性 §4、子图(Subgraphs)
Q6-7. Checkpoint、HITL、Time-Travel 在面试里怎么讲得更工程化?
这三个概念适合放在一起理解,因为它们共同解决的是:复杂 Agent 不是“能跑起来”就够了,还要能停、能看、能批、能继续。
- Checkpoint:解决“做到一半能不能恢复”,适合长任务、断点续跑、失败重放。
- HITL:解决“高风险动作要不要人工拍板”,适合支付、删改数据、对外发送这类动作前的人工确认。
- Time-Travel:解决“出问题后怎么回看和复盘”,适合排障、调试和重跑特定阶段。
如果用一句很工程化的话来概括,就是:
Checkpoint 负责可恢复,HITL 负责可控制,Time-Travel 负责可复盘。
这三个能力放在一起,面试官通常会感觉你理解的不是“图会不会画”,而是“系统上线以后怎么治理”。
常见追问:
- 哪些节点你一定会加人工审批?
- Checkpointer 和长期记忆 / Store 的边界怎么讲?
- 什么时候时间回溯只是调试利器,什么时候它已经是生产必需?
重要度:中频(出现次数:1次) | 难度:较难
考察点:LangGraph 生产能力理解,以及对恢复、审批、复盘三类能力的归纳能力。
对应章节:25 LangGraph高级特性 §2、状态持久化(Persistence);25 LangGraph高级特性 §3、时间回溯(Time-Travel)
7、记忆、MCP 与多智能体
Q7-1. 上下文窗口、短期记忆、长期记忆分别是什么关系?
上下文窗口是模型单次推理能看到的内容上限,短期记忆是会话级状态,长期记忆是跨会话保留的用户或任务信息。
工程上不能把这三者混为一谈。窗口是昂贵资源,不能无限塞历史。短期记忆一般保留当前任务相关历史、摘要、线程状态。长期记忆则更适合存偏好、画像、历史结论和可复用事实,需要按需检索,不应该每次全量放进 prompt。
所以一个成熟的系统通常是:窗口里只放高信号上下文,短期记忆保证当前线程不断档,长期记忆通过数据库或向量库按需召回。
常见追问:
- 记忆和 RAG 的区别是什么?
- 为什么说记忆不是训练模型?
- Redis 在记忆里适合做什么?
- 如果上下文太长被截断了,你会选滑动窗口、摘要压缩还是检索式补充?怎么判断?
重要度:必刷(出现次数:3次) | 难度:中等
考察点:对话系统和 Agent 的状态设计能力。
对应章节:16 记忆与对话历史 §1、记忆简介;25 LangGraph高级特性 §2、状态持久化(Persistence)
Q7-2. 如何为 Agent 设计短期记忆和长期记忆系统?技术上通常怎么落地?
先把“记忆”拆成两层,不然很容易把所有信息都塞进上下文,最后变成又贵又乱。
- 短期记忆:解决当前会话或当前线程不断档的问题,核心是“读历史 -> 拼进提示 -> 调模型 -> 写回历史”。
- 长期记忆:解决跨会话保留用户偏好、画像、历史结论和可复用事实的问题,核心是“存结构化信息,按需召回”,而不是每轮全量塞进 prompt。
技术上,短期记忆常见做法是把消息历史或线程状态持久化到内存、Redis 或 checkpointer 里,再通过 MessagesPlaceholder 或图状态在下一轮重新注入;长期记忆更适合放在数据库、向量库,或者在关系很重要的场景里放进知识图谱。一个比较稳的工程思路是:短期记忆服务当前任务连续性,长期记忆服务跨任务复用,二者分开设计。
在 LangChain / LangGraph 体系下,RunnableWithMessageHistory + BaseChatMessageHistory 更适合理解为“对话历史注入”,而 LangGraph persistence / checkpointer 更适合理解为线程级状态持久化。前者更适合先把“历史消息怎么读写”讲明白,后者更适合 Agent、图编排和线程级状态管理。
因此,真正落地时通常这样划分:
- 聊天助手或轻量多轮问答:优先消息历史方案。
- 复杂 Agent 或 LangGraph:优先
thread_id + checkpointer方案。 - 跨会话偏好、事实、用户画像:走长期存储,按需检索,不直接硬塞上下文。
如果继续追问“超长多轮对话如何兼顾不断档和节省 token”,通常还会补充四种常见手段:
- 滑动窗口:只保留最近 N 轮原文,简单直接,但容易丢早期约束。
- 周期性摘要:每隔几轮把历史压成结构化总结,保留目标、已确认事实、待办和偏好。
- 检索式补充:把旧对话写入向量库或长期存储,按当前问题召回相关片段,而不是把所有旧消息都塞回窗口。
- 结构化记忆槽:像城市、会员等级、偏好、关键状态这类固定信息直接落键值或数据库,不让模型每次“回忆”。
所以记忆系统不是“历史保存得越多越好”,而是要区分什么该原样保留,什么该摘要,什么该结构化,什么该按需检索。
常见追问:
RunnableWithMessageHistory和 LangGraph persistence / checkpointer 怎么理解各自的定位?- Redis 更适合拿来做短期记忆持久化,还是长期记忆?
重要度:高频(出现次数:2次) | 难度:较难
考察点:记忆分层设计能力,以及对短期状态、长期知识、外部存储边界的理解。
对应章节:16 记忆与对话历史 §4、RunnableWithMessageHistory 与 BaseChatMessageHistory;25 LangGraph高级特性 §2、状态持久化(Persistence)
Q7-3. 什么是 MCP?它解决的核心问题是什么?它和 Tool、RAG、Agent 有什么区别?
如果先给一句定义,MCP 是 Model Context Protocol,也就是模型上下文协议。它是一套开放标准,用来规范 AI 应用如何以统一方式连接外部工具、资源和提示模板。
MCP 本质上是在做“模型能力接入的统一协议层”,解决的不是“模型能不能调工具”,而是“不同 AI 应用怎样用统一方式接外部工具、资源和上下文”。
更直观的理解是,MCP 最适合被看成 AI 世界的统一插口。它统一的不是模型本身,而是 Host、Client、Server 之间发现、暴露和调用能力的方式。MCP Server 不只会暴露 Tools,还可以暴露 Resources 和 Prompts。
- Tool / Function Calling:解决模型如何表达“我要调用哪个工具、传什么参数”。
- RAG:解决模型如何拿到外部知识。
- Agent:解决谁来规划、决策、调用这些能力。
- MCP:解决这些外部能力如何被标准化暴露和接入。
所以 MCP 不等于 Agent,也不等于 Tool。它更偏协议和生态层,价值在于一次暴露、多处复用、统一 schema、降低接入成本。
常见追问:
- MCP Server 通常暴露哪些能力?
- STDIO 和 HTTP 传输怎么选?
- 为什么企业会关心 MCP?
重要度:高频(出现次数:1次) | 难度:中等
考察点:协议层理解能力。
对应章节:20 MCP模型上下文协议 §2、MCP 简介;20 MCP模型上下文协议 §3、MCP 能做什么
来源:腾讯AI后台工程师一面(实际大厂面试题)
Q7-4. MCP 和 Function Calling 的关系是什么?
Function Calling 更接近模型侧的“结构化调用机制”,MCP 更接近应用侧的“统一能力接入协议”。
前者解决的是模型如何表达“要调用哪个函数、传什么参数”;后者解决的是不同外部工具、资源、提示模板如何用统一标准暴露给客户端。两者不是替代关系,而是上下层关系。
可以简单理解为:Function Calling 更靠近模型决策,MCP 更靠近生态接入和协议标准化。真正落地时,Agent 完全可以在 MCP 暴露的能力之上继续用 Function Calling 做调用决策。
重要度:高频(出现次数:1次) | 难度:中等
考察点:协议层和调用机制的边界理解。
对应章节:20 MCP模型上下文协议 §2、MCP 简介;17 Tools工具调用 §2、工具调用的工作方式
Q7-5. MCP 是如何做到跨平台兼容的?
MCP 的跨平台兼容,本质上来自“统一协议,而不是统一实现”。
也就是说,不同平台、不同语言、不同宿主程序,只要都遵循同一套消息结构、能力暴露方式和调用约定,就能在协议层互通。这样客户端不需要为每一个外部系统写一套私有接入方式,服务端也能按标准暴露能力。
所以 MCP 的价值不在于它绑定了某个技术栈,而在于它把接入差异收敛成了统一的协议面。
重要度:中频(出现次数:1次) | 难度:中等
考察点:协议标准化的理解。
对应章节:20 MCP模型上下文协议 §5、MCP 架构知识
Q7-6. MCP 技术协议的主体内容是什么?
如果从协议层展开,MCP 的主体内容按四层来讲最清楚:
- 参与角色:Host、Client、Server。
- 能力类型:Tools、Resources、Prompts。
- 通信机制:能力发现、请求、响应、错误、通知。
- 传输方式:stdio、Streamable HTTP 等承载协议的通道。
这里还需要补充一句细节:
- Tools 更偏 model-controlled,适合让模型自动决定要不要调用。
- Resources 更偏 application-driven,通常由宿主决定如何纳入上下文。
- Prompts 更偏 user-controlled,适合复用提示模板或工作流模板。
这样答出来,面试官通常能看出你理解的是协议本身,而不是只把 MCP 当成某个“现成插件市场”。
重要度:中频(出现次数:1次) | 难度:中等
考察点:是否理解 MCP 协议层的基本构成。
对应章节:20 MCP模型上下文协议 §5、MCP 架构知识;20 MCP模型上下文协议 §3、MCP 能做什么
Q7-7. MCP 服务器开发流程是什么?
一个 MCP Server 的典型开发流程通常是:
- 明确要暴露的能力边界,是工具、资源还是提示模板。
- 定义输入输出 schema 和错误处理方式。
- 选择传输方式,比如 STDIO 或 HTTP。
- 实现服务端逻辑,并补齐鉴权、日志、超时和异常处理。
- 在客户端完成注册、发现、调用和联调测试。
工程上最容易被忽略的是能力边界和错误处理。MCP Server 不是简单把现有 API 套一层壳,而是要把能力设计成真正适合模型消费的接口。
重要度:中频(出现次数:1次) | 难度:中等
考察点:是否理解 MCP 不只是“会用”,还包括服务端建设。
对应章节:20 MCP模型上下文协议 §4、怎么用 MCP;20 MCP模型上下文协议 §6、案例实战:本地 MCP 天气服务与客户端
Q7-8. Agent 应该如何接入 MCP 工具?
Agent 接入 MCP 工具,核心是把 MCP 暴露出来的能力纳入自己的工具注册和调度体系。
通常的思路是:
- 先发现 MCP Server 暴露的工具或资源。
- 再把这些能力转换成 Agent 能消费的工具描述和 schema。
- 在执行时让模型决定何时调用,宿主负责真正发起 MCP 请求。
- 把结果回传给 Agent,进入下一轮决策。
真正的关键不在“接通”,而在“如何保证接通之后仍然可控”,包括权限、超时、失败重试、观测和审计。
常见追问:
- MCP 在你的架构里是只做权限检查,还是会和环境级安全沙箱一起工作?
- 如果已经有 MCP,为什么还需要容器、只读挂载或系统级隔离?
重要度:中频(出现次数:1次) | 难度:中等
考察点:Agent 架构与 MCP 的结合方式。
对应章节:20 MCP模型上下文协议 §4、怎么用 MCP;20 MCP模型上下文协议 §6、案例实战:本地 MCP 天气服务与客户端
Q7-9. MCP 的流式 HTTP 传输核心优势是什么?
流式 HTTP 的核心价值,是让结果可以边产生边返回,而不是必须等整次调用结束后一次性返回。
这样做有三个直接好处:
- 降低首包延迟,用户更快看到反馈。
- 支持长任务的中途进度回传,减少“卡死感”。
- 更利于宿主做取消、超时和部分结果处理。
对于 Agent 或研究型任务,这种能力尤其重要,因为很多调用本来就不是“瞬时完成”的。
重要度:中频(出现次数:1次) | 难度:中等
考察点:流式协议在体验和并发上的价值理解。
对应章节:20 MCP模型上下文协议 §5、MCP 架构知识
Q7-10. MCP 调用如何保证并发?
MCP 并发能力不只是协议本身决定的,更取决于宿主和服务端如何设计请求处理模型。
要保证并发,通常要关注:
- 请求是否有唯一标识,避免响应错配。
- 服务端是否支持并行处理,而不是串行阻塞。
- 工具执行是否有超时、限流和优先级控制。
- 客户端是否能正确管理多请求上下文。
如果底层工具本身很慢,协议再好也救不了,所以并发治理要看“协议层 + 服务层 + 工具层”三层一起设计。
重要度:中频(出现次数:1次) | 难度:较难
考察点:高并发调用下的协议和宿主设计能力。
对应章节:20 MCP模型上下文协议 §5、MCP 架构知识
Q7-11. A2A 和 MCP 的区别是什么?什么时候需要多智能体?
MCP 更关注“模型和外部能力怎么接”,A2A 更关注“Agent 和 Agent 之间怎么协作”。
前者偏工具与资源接入标准,后者偏多智能体通信和分工协作。如果系统只是需要接搜索、数据库、文件系统,MCP 的价值更明显;如果系统已经演化出多个角色明确、职责分离的 Agent,比如规划、检索、执行、审阅各自独立,那么 A2A 或多智能体编排才有意义。
多智能体不是越早越好。任务如果单 Agent 就能清晰处理,多智能体只会增加通信、调试和治理成本。只有在职责边界清晰、单 Agent prompt 已经过载、流程确实需要角色分治时,拆分才划算。
常见追问:
- Supervisor 和 Handoff 怎么选?
- 多智能体最常见的失败点是什么?
- 什么情况下 Skill 比独立 Agent 更合适?
- A2A 和 LangGraph 这类应用内编排框架、普通 Agent 框架的边界到底是什么?
- Agent Skills 和 Tool、独立 Agent 的边界怎么理解?什么时候更适合做成 Skill 而不是再拆一个 Agent?
重要度:中频(出现次数:1次) | 难度:较难
考察点:协议边界和系统拆分能力。
对应章节:26 LangGraph多智能体与A2A §1、A2A 协议与多智能体架构;26 LangGraph多智能体与A2A §2、Supervisor 与 Handoff;26 LangGraph多智能体与A2A §3、Skills 与多智能体的边界;27 Agent Skills智能体技能与AI编程工具实践
来源:腾讯AI后台工程师一面(实际大厂面试题)
Q7-12. 有哪些热门的 MCP 工具?
常见受关注的 MCP 工具通常集中在几类:
- 浏览器自动化类,比如 Playwright。
- 数据与后端类,比如数据库、Supabase 一类服务。
- 文件与代码类,比如本地文件系统、代码库。
- 搜索与知识类,比如网页搜索、知识库检索。
面试里更重要的不是背全名字,而是知道为什么这些工具受欢迎:因为它们覆盖了 Agent 最常见的外部能力需求。
重要度:低频(出现次数:1次) | 难度:基础
考察点:对 MCP 生态有基本了解,但不要求死记硬背。
8、平台实践与框架选型:Coze、Dify、RAGFlow
Q8-1. Coze / Dify 这类平台和 LangChain / LangGraph 的关系怎么理解?
Coze / Dify 更偏应用层、低代码和可视化交付,LangChain / LangGraph 更偏代码级开发框架。它们不是谁替代谁,而是同一条 AI 应用链路上的不同层级。
如果从这一层关系来理解:
- Dify / Coze 更适合快速把 Agent、RAG、Workflow 搭起来,方便业务侧验证、协作和交付。
- LangChain 更适合把 Prompt、Model I/O、Parser、Retriever、Tools 这些组件按代码方式组合起来。
- LangGraph 更适合状态显式、循环、条件路由、人机协同、多智能体这类复杂编排。
所以平台更接近“应用搭建层”,代码框架更接近“底层编排层”。真实项目里很常见的路径是:先用平台验证业务价值,再把核心链路迁到代码框架;或者平台负责上层业务编排,底层复杂能力由自建服务承接。
重要度:高频(出现次数:1次) | 难度:中等
考察点:平台能力和代码能力的选型判断。
对应章节:3 基于Coze&Dify平台的智能体开发 §1、智能体(AI Agent)概述;9 LangChain概述与架构 §2、LangChain 定位
Q8-2. 各类 Agent 开发框架应该如何选取?
框架选型不要看热度,要看任务复杂度、团队能力和交付要求。
- 如果目标是快速原型、生态丰富、常见组件齐全,LangChain 很合适。
- 如果任务有显式状态、循环、条件路由、人机协同或多智能体,LangGraph 更合适。
- 如果是低代码、快速验证业务流程,Coze / Dify 更适合。
- 如果系统边界简单、需求非常明确,直接手写 SDK 也可能是成本最低的方案。
更成熟的回答方式,不是简单比较“哪个最好”,而是说明“什么场景下用什么更划算”。能说出替代方案和迁移路径,通常比单纯夸某个框架更有说服力。
常见追问:
- 你最终会用哪些指标判断这次框架选型是不是成功的?
- 如果前期先用平台,后期迁到自研框架,迁移边界应该怎么划?
重要度:高频(出现次数:1次) | 难度:中等
考察点:框架选型和工程判断。
对应章节:9 LangChain概述与架构 §2、LangChain 定位;22 LangGraph概述与快速入门 §1、LangGraph 简介
Q8-3. 什么时候工作流比 Agent 更合适?
当业务步骤固定、输出格式明确、希望强可控和易审计时,工作流通常比 Agent 更合适。
比如分类、摘要、内容审核、报告生成、表单填充、固定审批链,这类场景流程路径通常是稳定的。用工作流能更清楚地定义节点职责、失败重试、人工介入点和 SLA。Agent 更适合开放任务和动态决策,而工作流更适合企业里大量“有规则、可追责、可回放”的流程型任务。
常见追问:
- Workflow 中哪些节点适合交给 LLM?
- 人工审核点应该加在哪里?
- 工作流如何逐步升级成 Agentic Workflow?
重要度:高频(出现次数:1次) | 难度:中等
考察点:流程治理意识。
对应章节:3 基于Coze&Dify平台的智能体开发 §6、工作流的搭建;21 Agent智能体 §1、Agent 简介
Q8-4. 用 Python 调用 Dify / Coze 工作流时,最值得关注哪些工程细节?
重点需要关注五类问题:鉴权、参数对齐、流式模式、超时限制、日志追踪。
先说 Dify。它的工作流 API 重点字段通常是:
Authorization: Bearer {api_key}- 请求体里的
inputs response_modeuser
这里有两个非常容易踩坑的点:一是 API Key 和工作流是绑定关系,不是拿一个全局 key 到处用;二是长流程尽量用 streaming,因为 blocking 很容易被网关超时打断。真正做流式时,要能识别 SSE 流里的 workflow_finished,并正确拿到最终 outputs。
再说 Coze。它更需要盯紧:
workflow_idapp_idparameters
其中 parameters 必须和工作流里定义的输入变量严格对齐,变量名一旦对不上,代码看起来能跑,请求也可能成功,但业务结果就是不对。流式处理时,则要能区分 PING、MESSAGE、DONE,或者 SDK 里的 MESSAGE / ERROR / INTERRUPT 事件。
如果从工程角度总结,重点需要看这几件事:
- 密钥、工作流、应用的绑定关系是否搞清楚。
- 请求参数名是否和平台工作流输入严格一致。
- 是不是默认用流式,而不是把长任务都压成阻塞式。
- 平台日志、
workflow_run_id、业务request_id是否串起来了。 - 如果流程里有外部副作用,是否做好幂等、重试和失败补偿。
这道题真正考察的,不是“会不会发 POST 请求”,而是是否真正理解过平台 API 的真实调用链。
常见追问:
- 流式和阻塞式返回怎么选?
- 平台日志能解决哪些排障问题?
- 平台工作流和自建后端服务怎么协作?
重要度:中频(出现次数:1次) | 难度:中等
考察点:是否看过平台 API 的真实调用链,而不是只会点界面。
对应章节:4 Python调用Dify平台工作流 §4、请求格式;5 Python调用Coze平台工作流 §3、通过 Python 代码调用工作流
Q8-5. 开源 RAG 平台比如 RAGFlow,应该如何选型?和 Dify 或自研链路怎么比较?
这道题不要答成“谁更强”,而要答成“当前知识库场景更需要什么”。
如果重点是文档导入、解析、切块、检索和 RAG 编排体验,那么像 RAGFlow 这类平台会比较有吸引力;如果重点是多类 Agent、工作流、插件生态和业务 API 编排,Dify / Coze 这类平台的边界会更宽;如果核心链路涉及深度定制、复杂状态治理、合规审计和内部系统耦合,自研代码链路通常更稳。
真正做选型时,优先看四类因素:文档导入与解析质量、权限和多租户能力、是否方便对接业务系统、二次开发空间。很多项目不是卡在“能不能做 RAG”,而是卡在“解析质量稳不稳、权限做不做得住、后续能不能接进企业系统”。
重要度:中频(出现次数:1次) | 难度:中等
考察点:RAG 平台选型判断,以及平台能力与自研能力的边界意识。
9、安全、评测、部署与可观测性
Q9-1. 你会如何评估一个 RAG 或 Agent 系统的效果?
评估先拆成离线评测和在线评测,再把问题拆到具体层级,而不是只盯一个总分。
如果是 RAG,通常按两层看:
- 检索层:相关内容有没有被召回,引用片段对不对,badcase 是卡在切块、召回、重排,还是过滤。
- 生成层:答案是不是忠实于证据,引用是否正确,无依据时能不能拒答,最终回答对业务有没有帮助。
如果是 Agent,评测应分成两类:
- 轨迹级评测:有没有选对工具、参数对不对、有没有无效循环、是不是过早结束。
- 结果级评测:最终任务有没有完成,输出质量和用户体验是否达标。
在线评测则看用户满意度、转人工率、任务中断率、耗时、badcase 类型分布。对于 Agent,还要看工具调用成功率、重试率、循环次数和人工兜底比例。
自动评测上,通常不会只押某一种方案,而是把规则评测、LLM-as-a-judge 和人工抽检结合起来。规则和 LLM 评测适合放大规模、做回归,人工抽检负责兜住高风险误判和评价漂移。
更重要的是评测不能只告诉你“分数变低了”,而要能定位到底是哪一层出问题。比如召回差、上下文组装差、生成幻觉、工具失败、终止条件不合理,这些问题的修法完全不同。
更推荐的评测闭环是:先分层建指标,再沉淀 badcase,最后让 badcase 反过来驱动 Prompt、检索、工具和流程迭代。
常见追问:
- RAGAS 这类框架适合做什么?
- 如何建设 badcase 数据集?
- 自动评测和人工评测怎么配合?
重要度:必刷(出现次数:2次) | 难度:较难
考察点:是否有评测闭环意识。
对应章节:19 RAG检索增强生成 §3、RAG 综合案例:智能运维助手;21 Agent智能体 §5、实操与案例
Q9-2. 什么是“幻觉”?应用层应该怎么缓解?
幻觉不只是“完全胡说八道”,更常见的是“看起来很像真的,但细节、引用、时间点、权限范围或者工具结果已经偏了”。
因此,不应把幻觉当成单纯的模型问题,而应把它视为一条链路上的系统问题。比较稳妥的缓解思路通常有四层:
- 知识层:用 RAG、外部检索、引用和无依据拒答,尽量让模型基于证据回答。
- 执行层:把计算、查库、发消息、查订单这类事情交给 Tool,不让模型纯文本瞎编结果。
- 约束层:用结构化输出、schema 校验、关键字段规则校验和低温度策略,减少“格式对了但内容错了”的情况。
- 治理层:做 badcase 回灌、人工抽检、线上监控和降级,别把“模型大概率没问题”当作上线条件。
总结来说:幻觉很难被彻底消灭,工程目标不是“保证永不出错”,而是让它 可发现、可限制、可回滚、可复盘。
常见追问:
- RAG 为什么不能彻底消灭幻觉?
- 什么叫“有引用但依然不忠实”?
- 幻觉和 Prompt Injection 是一回事吗?
重要度:高频(出现次数:2次) | 难度:中等
考察点:是否理解“模型能力问题”和“应用链路治理问题”的边界。
对应章节:19 RAG检索增强生成 §1、RAG 简介;14 输出解析器 §4、结构化输出
Q9-3. 多用户场景下,如何为 Agent 做安全沙箱隔离?
如果 Agent 只是单机 Demo,很多风险不会暴露出来;但一旦进入多用户场景,安全隔离就必须从“工具权限”上升到“执行环境隔离”。
一个更稳的方案通常会分三层:
- 环境层:用 Docker 或其他容器把每个任务或租户隔离开,限制 CPU、内存、网络和文件系统访问范围。
- 系统层:配合 seccomp、AppArmor、只读根目录、临时工作目录、非 root 用户运行,避免直接碰宿主机。
- 应用层:对工具、文件访问、系统命令、外部连接做白名单和审计,不让模型拿到裸权限。
如果要执行代码、命令或读文件,通常不会只靠 Prompt 或业务代码做限制,而是会把工作目录挂成最小权限空间,敏感目录不挂载,数据库密钥和主机凭据也不进容器。MCP 或 Tool 负责“能不能调”,沙箱负责“即使调了也跑不出边界”,两者职责不同,最好一起上。
重要度:高频(出现次数:2次) | 难度:较难
考察点:多用户 Agent 的安全隔离设计,以及协议层控制和环境层隔离的分工。
对应章节:7 企业级大模型部署 §1、企业级大模型部署概述;17 Tools工具调用 §6、从课程案例走向真实项目
Q9-4. Agent 部署和运维有哪些指标?
Agent 运维至少要看五类指标:
- 效果指标:任务完成率、用户满意度、引用正确率。
- 性能指标:首 token、端到端延迟、P95/P99。
- 稳定性指标:调用成功率、超时率、重试率、失败率。
- 成本指标:token 消耗、工具调用成本、单任务平均成本。
- 治理指标:人工接管率、审计日志完整性、异常任务复盘效率。
如果是多工具、多步骤 Agent,还要看每一步的成功率和卡点分布,否则你只能知道“最终失败了”,却不知道失败发生在哪一层。
重要度:高频(出现次数:1次) | 难度:中等
考察点:从 Demo 到生产的运维视角。
对应章节:7 企业级大模型部署 §1、企业级大模型部署概述;21 Agent智能体 §5、实操与案例
Q9-5. RAG 系统在实际部署中可能面临哪些挑战?
RAG 真正上线后,难点通常不在“能不能检索”,而在“这条从索引到回答的完整链路能不能长期稳定运行”。
这里首先要强调的是:RAG 不是单点能力,而是“索引阶段 + 检索与生成阶段”的工程链路。很多线上问题,本质上都是这两阶段里某一层出了问题。
先把挑战拆成六类:
- 数据侧:文档解析质量、切块策略、表格和 PDF 处理、知识更新时效。如果源数据脏、切块不合理,后面检索和生成都会被拖垮。
- 检索侧:Embedding 选型、召回质量、混合检索、重排效果、metadata 过滤。很多系统离线看起来能答,线上一放大就会暴露“召回不准、噪声太多、漏关键片段”的问题。
- 生成侧:上下文组装、引用溯源、忠实度控制、拒答策略。RAG 不是“检索了就不会幻觉”,如果上下文拼接差、Prompt 弱、引用不清,模型照样会编。
- 治理侧:权限隔离、版本管理、引用可追溯、知识回滚。企业里最怕的不是答错,而是答了权限外内容,或者答出来了却说不清依据来自哪里。
- 评测侧:badcase 建设、离线评测和在线指标闭环。没有评测,你很难知道问题到底出在切块、召回、重排还是生成。
- 工程侧:延迟、成本、缓存、可观测性、索引重建和线上运维。比如知识库一更新,是全量重建还是增量更新?查询慢是卡在检索、重排还是模型?这些都得拆开看。
所以面试里更稳的答法不是只说“部署会有很多工程问题”,而是明确讲出:RAG 是一条从数据接入、索引构建、检索召回到生成回答的完整链路,任何一层出问题,最终答案都会出问题。
常见追问:
- 如果 RAG 线上效果不好,你会优先从哪一层开始排查?
- 知识库频繁更新时,你怎么处理索引重建和版本回滚?
重要度:高频(出现次数:1次) | 难度:较难
考察点:是否有生产系统思维。
对应章节:19 RAG检索增强生成 §2、RAG 文本处理核心知识;7 企业级大模型部署 §1、企业级大模型部署概述
Q9-6. 如何确保 Agent 的行为安全、可控且符合人类意图?
这件事不能只寄希望于模型本身“够聪明”或“已经对齐过”。模型层的 SFT、RLHF / RLAIF 只能提供基础安全边界,真正落地到应用时,还必须补应用层治理。
通常会从四层做:
- 模型与提示层:用 System Message 明确角色、边界、安全规则和拒答策略。
- 工具与权限层:不给模型裸权限,所有 Tool 都要做 schema 约束、鉴权、参数校验和最小权限暴露。
- 执行与流程层:设置最大步数、超时、人工审批、转人工、失败兜底,避免无限循环和危险动作直接落地。
- 观测与审计层:保留调用日志、trace、输入输出、关键决策点和人工复盘链路。
如果再补一层生产视角,我还会把预算和熔断讲进去:
- 预算控制:最大工具调用次数、时间预算、token 预算、费用预算。
- 熔断机制:连续失败停止、异常率告警、重复无进展退出。
这些机制的价值不只是省钱,更重要的是把“模型可能一直试下去”的风险收住。
如果再往上一层总结,就是:模型对齐解决“基础行为别太离谱”,应用治理解决“在你的业务系统里别出事”。真正稳的 Agent,一定是“模型能力 + 权限边界 + 流程控制 + 人机协同”一起做,而不是只靠某一层。
常见追问:
- 模型已经做过对齐了,为什么应用层还要再做这么多安全治理?
- 哪些动作你会要求人工审批,而不是让 Agent 直接执行?
重要度:高频(出现次数:1次) | 难度:较难
考察点:是否理解“模型对齐”和“应用层安全治理”是两套互补机制。
Q9-7. Agent 链路耗时很长时,如何定位性能瓶颈?
Agent 一条链路跑到几分钟,不能只说“模型太慢”,要先把耗时拆开。
通常会按这几层做分解:
- 模型层:单次推理时长、首 token 延迟、输出 token 长度。
- 检索层:召回、重排、外部搜索、知识库请求耗时。
- 工具层:API 延迟、浏览器动作、代码执行、等待外部系统返回。
- 编排层:路由判断、Supervisor 判断、重复重试、串行等待。
- 结果层:后处理、校验、存储、日志落盘。
比较稳的做法是给每一步打 trace 和 span,先看 wall time 花在哪,再决定是换模型、改链路、减少步骤,还是把串行改成并行。如果用了 LangGraph,我还会看节点级耗时、是不是反复进入同一节点、有没有因为缺少明确完成信号而来回判断。
很多五分钟任务最后不是卡在单次模型调用,而是卡在多步链路过长、工具等待、监督节点重复判断,或者中间没有明确成功信号导致反复检查。也就是说,性能问题很多时候不是“模型太慢”,而是状态流转和执行闭环没有设计好。
重要度:高频(出现次数:1次) | 难度:较难
考察点:Agent 性能排障能力,以及把长链路延迟拆解到模型、工具、检索和编排层的能力。
Q9-8. 意图识别 / 任务路由应该怎么做?
需要先强调,这题和“模型路由”不是一回事。模型路由更接近“这次该用大模型还是小模型”,而意图识别 / 任务路由更接近“这次请求到底该走 FAQ、RAG、Tool、Agent、Workflow,还是直接转人工”。
比较稳的设计通常分三层:
- 第一层是目标分类:先定义清楚系统有哪些路由终点,比如闲聊、FAQ、知识问答、查状态、执行动作、复杂任务、人工兜底。
- 第二层是路由机制:高确定性场景先用规则和关键词兜底,模糊场景再用分类模型或 LLM 做语义判断。
- 第三层是置信度治理:低置信度不强判,允许澄清、降级或转人工,别让系统带着误判继续往下跑。
工程上通常不会做“单层万能路由器”,而是更偏好多级路由:
- 先做业务意图路由:判断这是 FAQ、RAG、Tool 还是 Agent。
- 再做子域路由:比如客服里再分订单、退款、工单、投诉。
- 最后再做模型路由:在具体链路里决定用强模型还是轻模型。
这样做的好处是更容易治理,也更容易评测。评估时重点看:路由准确率、低置信度占比、误路由成本、澄清命中率,以及每条路由分支的最终任务完成率。
常见追问:
- 什么时候先用规则路由就够了,什么时候必须上语义分类?
- 如果一个问题同时像 FAQ 又像 Tool 调用,你怎么处理?
- 如何避免错误路由把后续链路全部带偏?
重要度:高频(出现次数:1次) | 难度:较难
考察点:分层路由能力、意图识别落地能力,以及把 FAQ / RAG / Tool / Agent 串成统一入口的系统设计意识。
对应章节:17 Tools工具调用 §6、从课程案例走向真实项目;19 RAG检索增强生成 §3、RAG 综合案例:智能运维助手
Q9-9. 你会怎么做大模型应用的可观测性?
至少要做到三件事:有日志、有指标、有 Trace。也就是能看见请求怎么流过系统、每一步花了多久、失败在哪里、用了多少 token、最后输出了什么。
具体上,应打通 trace_id / request_id,把入口请求、Prompt 版本、模型版本、索引版本、检索结果、工具调用、异常信息、耗时和 token 用量串起来。如果用了 LangChain / LangGraph,还需要重点关注节点流转、状态更新、人工中断点、重试次数和最终退出原因。
对 Agent 系统来说,可观测性尤其重要,因为很多问题都发生在中间步骤,而不是最终输出那一刻。真正有用的可观测性,不只是“最后报错了”,而是能告诉你:它在哪个节点卡住、为什么重试、为什么结束、当时看到了什么状态。
重要度:中频(出现次数:1次) | 难度:中等
考察点:Trace、日志、指标意识。
Q9-10. 控制大模型应用成本,你会优先从哪些层面下手?
通常从模型、上下文、检索、流程四层入手。
- 模型层:高低配模型分层,大任务用强模型,小任务用轻模型。
- 上下文层:压缩 Prompt,减少无效历史,控制返回长度。
- 检索层:优化召回和重排,减少无意义进窗内容。
- 流程层:减少不必要的 LLM 调用次数,能规则化就不要全靠模型。
很多系统成本高,不是因为模型太贵,而是链路设计太浪费,比如无差别全量检索、过长历史拼接、多次重复调用大模型、工具失败后盲目重试。
常见追问:
- 什么时候应该引入模型路由?
- 如何平衡成本和效果?
- token 成本和工具成本哪个更值得先优化?
重要度:中频(出现次数:1次) | 难度:中等
考察点:成本优化思维。
对应章节:1-1 大模型认知与工程概览 §4、大模型的工程实现(概览);7 企业级大模型部署 §1、企业级大模型部署概述
Q9-11. 模型路由应该怎么做?
模型路由的核心不是“让所有请求自动找最强模型”,而是让不同复杂度、不同风险等级的请求走到最合适的链路。
比较常见的做法有三层:
- 意图路由:先判断这是 FAQ、RAG、工具调用、复杂 Agent,还是应该直接转人工。
- 复杂度路由:简单分类、格式化抽取、轻量总结走小模型;多步规划、开放生成、复杂决策走大模型。
- 成本路由:热点问题先查缓存,能规则化解决的就别进大模型,高价值用户或高风险任务再给更强模型预算。
真正落地时,通常不会把“路由”做成纯主观规则,而会配合评测和线上数据来调:
- 看不同路由分支的完成率、延迟、每任务成本。
- 看是不是某类问题被错误地下沉到了小模型。
- 看路由规则是否需要引入回退机制,比如小模型失败后升级到大模型。
所以模型路由本质上是效果、成本和稳定性之间的平衡器。做得好的系统,不是每次都用最贵的模型,而是能把“该省的地方省掉,该花的地方花对”。
常见追问:
- 什么场景下适合先用规则路由,而不是一开始就做模型路由?
- 小模型路由失败后,怎样设计升级和回退策略?
- 如何避免为了省成本把系统整体效果打穿?
重要度:中频(出现次数:1次) | 难度:较难
考察点:成本治理、模型分层和路由策略设计能力。
对应章节:11 Model I/O 与模型接入 §2、LangChain 模型分类、参数与返回;7 企业级大模型部署 §1、企业级大模型部署概述
Q9-12. 内网环境下怎么设计 Agent / RAG 系统?
内网环境下做 Agent / RAG,核心不是“把公网方案照搬一遍”,而是先把 数据不出域、模型可控、依赖可替换、链路可观测 这几件事立住。
如果按企业级部署思路设计,通常拆成三层:
- 应用层:Dify、Coze 类平台的私有化版本,或者自研后端服务,负责工作流、Agent 编排、权限和业务接口。
- 推理层:用 Xinference 这类托管平台统一管理 LLM、Embedding、Rerank,对外暴露 OpenAI-compatible API。
- 数据层:内网向量库、关系库、对象存储、知识库索引、日志和审计系统都放在可控域内。
这类架构的关键点一般有:
- 模型接入统一:上层尽量都走兼容 API,方便替换在线模型、本地模型和不同推理引擎。
- 网络和权限隔离:Agent 能访问哪些内网系统,必须按最小权限开放,不让模型拿到一把通吃的内网钥匙。
- 知识治理:文档解析、索引重建、版本回滚、租户隔离、引用追踪都要内建,不然内网知识库很快会失控。
- 可观测与审计:请求链路、工具调用、检索结果、模型版本、token 用量、异常告警都要能在内网闭环查看。
- 降级能力:内网模型抖动时,是否有缓存、规则链路、人工兜底或备用模型,不然系统会很脆。
总结来说:内网方案不是“离线版 Demo”,而是一套更强调安全边界、运维治理和模型可替换性的生产架构。
常见追问:
- 为什么很多企业会选择“应用层 + 推理层”解耦,而不是把所有东西混在一台机器上?
- 内网环境下,LLM、Embedding、Rerank 为什么最好分开部署和管理?
- 如果不能直接访问公网搜索,动态信息怎么补?
重要度:中频(出现次数:1次) | 难度:较难
考察点:企业私有化部署理解、应用层与推理层解耦意识,以及内网 Agent / RAG 的安全与运维设计能力。
对应章节:7 企业级大模型部署 §1、企业级大模型部署概述;7 企业级大模型部署 §3、模型部署;12 Ollama本地部署与调用 §5、LangChain 整合 Ollama
10、典型业务场景设计题
Q10-1. 如果让你设计一个企业知识库问答系统,你会怎么做?
通常按“数据接入、检索链路、生成链路、治理能力”四层设计。
先把文档接入、解析、切块、向量化和索引更新机制搭好,再设计查询链路,包括 query 改写、混合检索、rerank、权限过滤和上下文组装。生成层要强调“基于证据回答、无依据时拒答、尽量带引用”。治理层则要补上权限、版本、监控、评测和 badcase 回灌。
如果是企业场景,通常还会预留转人工、知识修订、索引回滚和多租户隔离能力,因为这些往往比“第一次答对”更影响长期可用性。
常见追问:
- 如何支持知识更新?
- 如何避免用户问到权限外文档?
- 如果系统答非所问,优先排查哪里?
重要度:必刷(出现次数:1次) | 难度:较难
考察点:从需求到架构的完整表达能力。
对应章节:2-RAG 搭建企业私有&个人知识库 §5、使用 Dify 搭建知识库;19 RAG检索增强生成 §3、RAG 综合案例:智能运维助手
Q10-2. 如果让你设计一个 NL2SQL Agent,你会重点控制哪些风险?
重点应控制 schema 注入、SQL 安全、执行验证和歧义澄清四类风险。
首先不能把整个数据库 schema 全量塞进上下文,而是要按问题做裁剪和 schema linking。其次要限制只读查询、禁止危险语句、加执行沙箱和超时控制。再次要做语法检查和执行前校验,避免生成看似合理但跑不通的 SQL。最后,对时间范围、口径定义、字段歧义这类问题,要支持澄清,不要让模型自行脑补。
常见追问:
- 生成 SQL 前要不要先展示预览?
- 如何做 self-correction?
- 什么时候该放弃生成 SQL,转人工或转报表模板?
重要度:高频(出现次数:1次) | 难度:较难
考察点:结构化查询和安全意识。
Q10-3. 如果让你设计一个客服智能体,你会优先考虑哪些能力?
优先考虑意图识别、知识检索、流程执行、人机协同和复盘闭环。
客服场景不是简单问答,它往往同时涉及 FAQ、订单查询、退款流程、工单流转和情绪安抚。所以系统既要能回答,也要能查状态、调接口、补资料,还要在不确定时及时转人工,并把上下文完整交接过去。
上线后还要持续复盘高频未解决问题、误判意图、知识缺口和转人工原因,这样系统才会越做越稳,而不是只在演示时好看。
常见追问:
- 转人工条件怎么设计?
- 客服场景为什么更需要长期记忆或用户画像?
- 如何做满意度和闭环评估?
重要度:高频(出现次数:1次) | 难度:较难
考察点:业务型 Agent 设计能力。
对应章节:3 基于Coze&Dify平台的智能体开发 §7、项目:商户运营管家;21 Agent智能体 §5、实操与案例
Q10-4. 如何设计 Agent 的完成判断与终止条件,避免过早结束?
Agent 的“结束”不能只靠感觉,也不能只靠看日志或一个宽泛的完成率分数。更稳的做法是给它设计显式的完成条件。
终止逻辑通常拆成四层:
- 任务层:是否达成用户目标,是否拿到了必须的产物或字段。
- 工具层:关键工具调用是否成功,返回结果是否满足预期约束。
- 状态层:当前 state 是否进入 done / failed / need_human 这样的终态。
- 保护层:最大步数、超时、重复无进展次数、异常退出条件。
如果链路里有 Supervisor 或监督节点,更适合把它当“辅助审查”和“异常兜底”,而不是唯一的结束依据。真正稳的方案是:只有当关键任务结果、工具返回和状态机条件都满足时,才允许进入最终答复节点。否则宁可继续执行、澄清或转人工,也不要过早把未完成结果返回给用户。
常见追问:
- 只靠监督节点统计完成率和查日志,为什么会慢、还容易误判?
- 有没有更通用的判断方式,比如直接看关键工具调用结果和状态字段?
- 如何避免 Agent 任务还没做完,就提前返回给用户?
重要度:高频(出现次数:1次) | 难度:较难
考察点:Agent 终止条件设计、状态机思维,以及 Supervisor / 工具结果 / 状态字段的组合使用能力。
对应章节:24 LangGraphAPI:节点、边与进阶 §2、Graph API 之 Edge;21 Agent智能体 §4、Agent 工作原理(V1.0)
Q10-5. Code Agent 怎么设计?
Code Agent 的核心不是“会写几行代码”,而是能围绕一个代码任务完成 读代码库、定计划、改文件、跑验证、根据结果继续修正 的闭环。从系统结构看,它本质上仍然是一个 模型 + 工具集 + 运行循环 + 当前状态 的执行系统,只不过工具和状态都更偏代码场景。
如果按模块拆分,通常设计为以下几层:
- 任务理解层:先把需求转成明确目标,判断是读代码、改 bug、补测试、重构,还是回答架构问题。
- 代码库理解层:通过代码搜索、文件树、符号索引、依赖关系、提交历史建立最小必要上下文,而不是把整个代码库一股脑塞进模型。
- 计划与执行层:先列出修改计划,再决定读哪些文件、改哪些文件、跑哪些命令、需要哪些验证。
- 工具层:至少要有搜索、读文件、编辑文件、运行测试、执行命令、查看日志这些能力。
- 反馈闭环层:根据 lint、测试、运行报错、diff 结果继续修正,而不是生成一次补丁就结束。
- 安全治理层:限制执行权限、隔离危险命令、保留变更和执行日志,避免 Agent 在代码库里无约束乱改。
真正的难点通常不在“会不会生成代码”,而在三个地方:
- 上下文治理:代码库很大时,怎么只拿到和当前任务最相关的上下文。
- 验证闭环:怎么判断修复真的生效,而不是只让模型说“我已经改好了”。
- 失败处理:当 Agent 连续修不好 bug 时,怎么停下来、怎么暴露中间诊断、怎么交还给人。
所以一个成熟的 Code Agent,更接近“面向代码任务的执行系统”,而不是“代码自动补全工具放大版”。它的关键不只是会写代码,而是能把 代码库理解、工具调用、验证反馈、停止条件 做成稳定闭环。
常见追问:
- 千万行代码库里,Code Agent 最容易先卡在哪里?
- Agent 修不好 bug 时,你怎么设计停止条件和人工接管?
- 静态知识和动态数据在代码场景里怎么区分,哪些该靠索引,哪些该靠运行反馈?
重要度:高频(出现次数:1次) | 难度:较难
考察点:代码场景下的 Agent 设计能力、代码库上下文治理能力,以及“修改 - 验证 - 继续修复”的闭环意识。
Q10-6. 如果让你设计一个 Deep Research 系统,它和普通 RAG 有什么不同?
普通 RAG 更接近“先检索,再回答”,而 Deep Research 更接近“围绕一个复杂主题,先拆研究任务,再多轮检索、交叉验证、综合整理,最后输出结构化报告”。
因此,Deep Research 通常需要查询规划、子任务拆分、多源检索、冲突检测、引用管理、可信度判断和迭代补检这些能力。它更接近研究助手,而不是问答助手。输出形式也更偏报告、摘要和引用列表,而不是一句直接答案。
如果按工程实现去理解,它本质上更接近“工作流 / 图编排 + 多轮检索 + 报告生成”的组合能力,而不是把普通 RAG 的上下文塞得更长。重点不只是召回,而是有没有把研究过程拆开、证据有没有被反复求证、最后能不能形成带引用的结构化结论。
常见追问:
- 如何保证结果可靠?
- 什么叫多源交叉验证?
- 这类系统为什么更需要人工审核点?
重要度:中频(出现次数:1次) | 难度:中等
考察点:复杂检索与报告生成理解。
Q10-7. 如何设计一个 Deep Research 系统?
Deep Research 的核心不是“查一次资料再总结”,而是“围绕复杂问题持续研究并形成结构化结论”。
一个比较完整的设计通常包括:
- 查询规划器:把复杂问题拆成子问题。
- 检索执行器:面向搜索、数据库、知识库做多源检索。
- 信息融合层:做去重、冲突检测、证据对齐和可信度加权。
- 迭代控制器:发现证据不足时继续补检,而不是一次性结束。
- 报告生成器:输出摘要、正文、引用和时间戳。
如果采用更工程化的表达方式,可以将其理解为一个研究型工作流:
- 前面是任务拆解和检索编排。
- 中间是多轮证据收集、去重、冲突处理。
- 后面是结构化报告生成和引用整理。
它和普通 RAG 最大的差异,是从“单轮问答”升级成“多轮研究流程”。所以设计重点不只是召回率,还包括规划质量、引用质量、可信度和过程可回放性。真正落地时,通常也更倾向于用 Workflow 或 LangGraph 这类显式编排方式来做,而不是把它完全交给一个黑盒对话 Agent。
重要度:中频(出现次数:1次) | 难度:较难
考察点:复杂研究型系统的模块拆分能力。
Q10-8. 如何设计 Python 代码解释器?
Python 代码解释器本质上是“模型生成代码 + 安全执行 + 回传结果”的闭环系统。
核心设计通常包括:
- 代码生成层:模型把用户需求转成 Python 代码。
- 沙箱执行层:隔离环境、白名单模块、超时限制、资源限制。
- 结果回传层:收集 stdout、stderr、文件产物、图表和返回值。
- 状态管理层:支持多轮执行时保留变量或工作目录。
- 安全治理层:禁止危险系统调用、网络访问和越权读写。
这类系统真正的难点不在“会不会运行 Python”,而在“如何既让它有执行能力,又不把宿主环境暴露出去”。
常见追问:
- 如果 Agent 需要执行系统命令或访问文件,你怎么防止它越权删除数据库、读取主机敏感文件?
- 除了白名单,你在宿主环境隔离上还会做哪些限制?
重要度:中频(出现次数:1次) | 难度:较难
考察点:执行型智能体和安全沙箱设计。
对应章节:17 Tools工具调用 §6、从课程案例走向真实项目;21 Agent智能体 §5、实操与案例
Q10-9. 如何设计类 Manus 通用智能体?
这类系统不宜先强调“通用”,而应先回到 Agent 的最小骨架:模型 + 工具集 + 运行循环 + 当前状态。所谓类 Manus,本质上是把这四层能力扩展到更长任务链和更复杂任务空间中。
设计上通常要有:
- 任务理解与规划模块:把目标拆成可执行步骤。
- 工具调度模块:统一接搜索、浏览器、代码执行、文件系统等工具。
- 状态与记忆模块:维护任务上下文、待办和阶段成果。
- 执行与反思模块:根据中间结果继续执行、修正或重试。
- 安全与治理模块:权限控制、日志、失败恢复和人工接管。
这类系统和普通 Agent 的区别,不在于“工具更多”,而在于它需要长期推进任务、处理中间产物、管理复杂状态,并在较长链路里维持稳定性。因此,它更接近一个通用任务执行框架,而不是单轮问答机器人的放大版。
重要度:中频(出现次数:1次) | 难度:较难
考察点:通用任务型 Agent 的系统拆分能力。
Q10-10. 如何开发浏览器自动化 Agent?
浏览器自动化 Agent 的核心是把“看页面、理解页面、操作页面、校验结果”做成一个稳定闭环。
一个比较合理的设计通常包括:
- 页面感知层:拿到 DOM、截图、可交互元素信息。
- 决策层:根据目标决定点击、输入、滚动、等待还是回退。
- 执行层:调用浏览器自动化工具完成动作。
- 校验层:确认动作是否生效,页面是否进入预期状态。
- 治理层:超时、重试、幂等、防误操作和日志回放。
这类 Agent 最大的难点不在“会不会点按钮”,而在网页不稳定、异步加载、状态漂移和误操作成本高,所以一定要把验证和回滚意识做进去。
重要度:中频(出现次数:1次) | 难度:较难
考察点:浏览器工具、任务规划和可靠性设计。
Q10-11. 当 Agent 需要在真实或模拟环境中执行任务时,它和纯软件工具型 Agent 有什么本质区别?
最大的区别在于:软件型 Agent 主要在数字系统里操作,输入输出更结构化,可回放性也更强;而真实或模拟环境里的 Agent,面对的是感知噪声、状态不完备、实时反馈和物理世界副作用。
也就是说,纯软件工具型 Agent 更接近“调接口、读写数据、操作页面”,重点是权限、状态机和工具编排;真实环境 Agent 则多了感知、定位、动作执行和安全约束,很多时候不是“调用成功”就算完成,而是还要判断动作有没有真正生效、环境有没有变化、风险是否可控。
所以这类系统会更强调闭环感知、实时性、仿真验证、失败保护和人工接管。它本质上不是把工具再多接几个,而是把 Agent 从“信息处理系统”推进到了“感知-决策-行动闭环系统”。
重要度:中频(出现次数:1次) | 难度:较难
考察点:对“软件型 Agent”和“行动型 / 具身环境 Agent”边界的理解。
11、项目表达与面试实战
Q11-1. 面试官让你介绍一个做过的 RAG / Agent 项目,你应该怎么讲?
更稳的表达顺序是 业务目标 -> 方案设计 -> 你负责的部分 -> 难点 -> 指标结果。
不要一开始就报技术栈,而要先讲这个项目解决了什么业务问题,例如“降低客服查文档成本”或“让运营能自然语言查数”。然后再讲架构,比如用了 RAG、Tools、LangGraph 还是平台工作流。接着重点说你自己具体做了什么,比如索引策略、权限设计、工具接入、评测体系、成本优化。最后给出可量化结果,比如命中率、耗时、转人工率、人工节省时长。
如果希望表达更稳,可以采用适合应用岗的轻量 STAR 结构:
- 情境:业务问题和约束是什么。
- 任务:你负责哪一段,目标指标是什么。
- 行动:你具体做了哪些设计、排障、优化。
- 结果:最终指标、上线效果、复盘收获。
面试官在项目题里最常往下追的,通常不是“你用没用过某个框架”,而是这些方向:
- 你具体负责哪一层,别把团队成果全讲成自己做的。
- 遇到过哪些 badcase,最后怎么修。
- 为什么这么选型,而不是别的方案。
- 项目上线后效果怎么评估,问题怎么排查。
常见追问:
- 这个项目最难的点是什么?
- 你做过哪些 trade-off?
- 如果重做一次,你会怎么改?
重要度:必刷(出现次数:1次) | 难度:中等
考察点:项目表达能力。
对应章节:19 RAG检索增强生成 §3、RAG 综合案例:智能运维助手;21 Agent智能体 §5、实操与案例
Q11-2. 如果面试官问“你们为什么不用某个更热门的框架或模型”,怎么回答更稳?
回答重点不是证明“当前选择永远最对”,而是说明“当前选择和业务约束是匹配的”。
比较完整的回答可以按四层展开:
- 业务复杂度:当前问题到底需要单 Agent、Workflow,还是已经复杂到必须上 LangGraph / 多智能体。
- 工程约束:团队熟悉度、交付周期、可观测性、可维护性、迁移成本。
- 部署条件:是直接用在线模型,还是要接 OpenAI 兼容 API、本地模型、Ollama、Xinference 这类自建推理层。
- 成本与性能:延迟、吞吐、token 成本、模型版本稳定性、是否存在第三方版本漂移风险。
比如没选更重的多智能体框架,可能是因为当前任务用单 Agent 或 Workflow 已经够了;没选更大的模型,可能是因为延迟和成本不划算;没选某个托管平台,可能是因为我们更看重统一 OpenAI-compatible API、模型可控和私有化部署能力。
再往前走一步,还应该顺手体现两点:
- 你知道替代方案,不是没调研过。
- 你给当前方案留了迁移路径,不会把系统锁死。
常见追问:
- 如果未来需求变复杂,迁移路径是什么?
- 你们是如何做技术验证的?
- 如何避免技术选型锁死?
- 如果你的 MCP 是基于 Java 自研的,和成熟 Agent 框架相比,扩展性优势到底体现在哪?
重要度:高频(出现次数:1次) | 难度:中等
考察点:技术判断和表达成熟度。
对应章节:9 LangChain概述与架构 §2、LangChain 定位;7 企业级大模型部署 §1、企业级大模型部署概述
Q11-3. 如果线上效果波动很大,你怎么向面试官展示你的排障能力?
最重要的是分层排查,并且给出清晰的证据链。
更规范的答法是:先确认是否存在输入分布变化,再看模型版本或 Prompt 是否变更,然后检查检索召回、工具调用、上下文组装、接口超时和日志链路。如果是 RAG,应抽样查看 query、召回片段和最终答案是否一致;如果是 Agent,则需要检查每一步工具调用和状态推进是否异常。最后再看是不是评测集失真或缓存污染。
这种回答比泛泛地说“调参数、优化 Prompt”更有说服力,因为它体现的是分层排障能力。
常见追问:
- 有没有遇到过偶发性错误?
- 如何做问题复现?
- 如何做一次有效复盘?
- 你会怎么判断问题主要来自模型幻觉,还是 RAG、Prompt、工具链路本身?
重要度:中频(出现次数:1次) | 难度:中等
考察点:故障定位与复盘能力。
12、工具生态与岗位认知
Q12-1. 你常用哪些 AI 开发工具?各自适合什么场景?
比较稳的答法,不是罗列名字,而是按场景分层。
通常可以分成几类:
- 通用对话与研究类:例如
ChatGPT、Claude、Perplexity,适合快速验证想法、梳理方案、写初稿、做资料总结。 - AI 编码协作类:例如
Cursor、Claude Code、GitHub Copilot,适合读代码、改代码、补测试、解释代码库结构,重点看它是更偏 IDE 内协作,还是更偏终端里的代码库级 Agent。 - 低代码 / 工作流平台类:例如
Coze、Dify,更适合业务快速验证、工作流编排、知识库接入和多人协作。 - RAG / 知识库平台类:例如
RAGFlow,更适合文档导入、解析、切块、检索和引用链路验证。
如果放到真实项目里,通常不会只选一种工具,而是按阶段组合使用。比如前期用通用对话工具梳理方案,用低代码平台做 POC,用 AI 编码工具接手核心链路实现,后面再把评测、部署和可观测性补齐。
如果需要一句更像面试现场的话,可以直接这样概括:方案梳理和资料总结常用 ChatGPT、Claude 这类通用工具;代码实现和仓库级修改更偏向 Cursor、Claude Code、Copilot;业务验证和工作流编排更常用 Dify、Coze;知识库链路验证则更适合 RAGFlow 这类平台。
这类问题真正考察的,不是“用过多少工具”,而是是否形成了自己的工具分层心智,知道不同工具各自解决什么问题。
重要度:中频(出现次数:1次) | 难度:基础
考察点:工具生态认知,以及是否能按场景而不是按热度选工具。
来源:腾讯AI后台工程师一面(实际大厂面试题)
Q12-2. Cursor、Claude Code、Copilot 这类 AI 编码工具的差异该怎么理解?
可以将它们理解为“都在帮助开发,但协作位置和交互方式不同”。
- Cursor 这类工具更偏 IDE 内协作,适合一边看代码、一边补全、局部改写、基于当前文件或工作区做编辑。
- Claude Code 这类工具更偏终端里的代码库级 Agent,适合跨文件理解、执行命令、改一组文件、做更完整的任务闭环。
- Copilot 这类工具更偏轻量级代码补全和编辑辅助,优势通常在低打断、嵌入现有编辑器工作流。
因此,不宜简单判断谁更强,而应回到任务类型本身。如果是“边写边补全、快速改一段代码”,IDE 内工具体验更顺;如果是“要理解整个代码库、跑命令、跨文件修改、完成一个更完整任务”,终端型 Agent 更有优势;如果团队已经有稳定 IDE 习惯,轻量补全类工具的接入成本也更低。
还可以补一句:工具能力会持续变化,所以真正稳定的判断标准不是名字,而是它对代码库上下文的理解深度、执行边界、可控性,以及是否契合团队工作流。
重要度:中频(出现次数:1次) | 难度:基础
考察点:AI 编码工具的比较能力,以及是否有稳定的选型标准。
来源:腾讯AI后台工程师一面(实际大厂面试题)
Q12-3. 如果让你总结“AI 应用工程师”和“算法研究岗”的差异,你会怎么说?
可以概括为,AI 应用工程师更关注“把模型能力稳定、安全、低成本地交付到业务系统中”,而算法研究岗更关注“如何改进模型本身的能力边界”。
前者的核心工作通常是技术选型、RAG / Agent 架构、工具和数据接入、工作流编排、评测、部署、监控和业务闭环;后者则更偏模型训练、微调策略、数据构造、算法实验和指标突破。两者会有交集,但能力重点不一样。
重要度:低频(出现次数:1次) | 难度:基础
考察点:岗位认知是否准确。
新手入门与常见问题
新手入门与常见问题
这份文档是本仓库的新手入口。它不只是告诉你怎么 pip install,更重要的是帮你先搞清楚:这个仓库有哪些内容、应该从哪里开始、哪些案例能直接跑、哪些项目需要切到配套源码仓库,以及遇到报错时该按什么顺序排查。
如果你第一次接触 AI 智能体开发,建议先阅读本文,完整章节结构见 教程目录大纲。
1、项目是什么?我能做什么?
本仓库是 《AI 智能体实战速成指南:从零到企业级落地》 的教程主仓,目标是做一套由浅入深、通俗易懂、能跑代码、能做项目、能复盘面试的 Python 智能体学习资料。
它现在主要包含五类内容:
| 内容 | 位置 | 什么时候看 |
|---|---|---|
| 系统教程正文 | 根目录下的 1-1 到 26 章 Markdown 文档 | 想系统学习大模型、RAG、Agent、LangChain / LangGraph 时 |
| Coze / Dify 工作流案例 | 案例与源码-1-Coze&Dify工作流智能体/ | 想先用低代码平台做出可演示应用时 |
| LangChain / LangGraph 代码案例 | 案例与源码-2-LangChain框架/、案例与源码-3-LangGraph框架/ | 想在本仓库里直接运行 Python 小案例时 |
| 企业级实战项目教程 | 实战项目-电商问数/、实战项目-深度研搜/ | 想把零散知识点串成完整项目时 |
| 导航、术语、面试与资料 | 教程目录大纲.md、章节索引与跳转指南.md、全书术语表.md、AI智能体与大模型应用开发面试题库.md、工具导航与参考资料索引.md | 查漏补缺、复盘表达、准备面试时 |
2、第一次学习,推荐怎么走
2.1 先按目标选路线
| 你的目标 | 推荐起点 | 推荐主线 | 适合人群 |
|---|---|---|---|
| 零基础系统入门 | 第 1-1 章 | 1-1 -> 1-2 -> 1-3 -> 2 -> 3 -> 9 -> 10~21 -> 22~26 | 对大模型、RAG、Agent 还没有完整认知的同学 |
| 尽快跑通第一个代码案例 | 第 10 章 | 10 -> 11 -> 13 -> 14 -> 15 -> 17 -> 21 -> 22 | 已有 Python 基础,想快速进入代码实践的同学 |
| 做项目或准备面试 | 第 9 章 | 9~26 -> 电商问数 -> 深度研搜 -> 面试题库 | 需要“原理 + 实操 + 项目表达”一起建立的同学 |
2.2 零基础系统入门
如果你还分不清大模型、提示词、RAG、Agent、LangChain、LangGraph,建议按下面顺序:
1-1 大模型认知
-> 1-2 提示词工程
-> 1-3 RAG、微调、续训与智能体选型
-> 2 RAG 知识库入门
-> 3 Coze / Dify 平台体验
-> 9 LangChain 概述
-> 10~21 LangChain 主线
-> 22~26 LangGraph 主线
这个路线的重点不是一上来写复杂代码,而是先建立认知地图:知道每个技术名词解决什么问题,后面跑案例才不会像在背 API。
2.3 已有 Python 基础,想尽快跑代码
适合已经会基本 Python,希望尽快进入框架实践的同学。
9 LangChain 概述
-> 10 HelloWorld
-> 11 Model I/O
-> 13 Prompt
-> 14 Parser
-> 15 LCEL
-> 17 Tools
-> 19 RAG
-> 21 Agent
-> 22 LangGraph
对应源码主要在:
案例与源码-2-LangChain框架/
案例与源码-3-LangGraph框架/
2.4 想做一个能写进简历的项目
建议先跑完 LangChain / LangGraph 主线,再进入实战项目:
2.5 按时间预算学习
如果时间比较紧,可以先按下面节奏安排:
| 时间 | 建议学法 |
|---|---|
| 7 天速通 | 1-1 -> 1-2 -> 9 -> 10 -> 11 -> 13 -> 14 -> 15 -> 17 -> 21 -> 22,重点是跑通最小代码闭环 |
| 15 天系统入门 | 前 3 天学基础认知,2 天体验 Coze / Dify,6 天吃透 LangChain 主线,最后 3 天进入 LangGraph |
| 90 天项目 / 求职 | 第一周打基础,第二周跑 LangChain / RAG / Agent,第三周学 LangGraph、实战项目和面试题 |
不管选哪条路线,最重要的动作都一样:每学完一章,至少跑一个案例;每学完一个阶段,用自己的话复述一次。
2.6 什么时候可以进入下一阶段
| 当前阶段 | 进入下一阶段前,至少做到什么 |
|---|---|
| 基础认知 | 能区分 Prompt、RAG、Agent、微调,不再把它们混成一个词 |
| 低代码平台 | 能独立搭一个简单 Coze / Dify 工作流,并知道平台拖拽和代码开发的边界 |
| LangChain 主线 | 能写出 prompt -> model -> parser,并接一个 Tool 或简单 RAG |
| Agent 阶段 | 能解释“固定流程”和“模型自主决策”的区别,知道什么时候该用 Agent |
| LangGraph 阶段 | 能把一个任务拆成 State、Node、Edge,而不是只写线性脚本 |
| 多智能体阶段 | 能判断什么时候值得拆多个 Agent,什么时候保持单 Agent 更稳 |
3、环境准备:运行本仓库案例
3.1 Python 版本
根目录的 requirements.txt 和 pyproject.toml 已经约束了 Python 版本:
| 项目 | 说明 |
|---|---|
| 推荐版本 | Python 3.10 |
| 支持范围 | Python 3.10 到 Python 3.13 |
| 暂不支持 | Python 3.14 |
原因是根目录案例依赖了 langchain-redis 等包,这些依赖暂时还没有完整兼容 Python 3.14。
先检查本机版本:
python3 --version
如果电脑里有多个 Python,建议显式使用 python3.10。
3.2 创建虚拟环境
在项目根目录执行,也就是能看到 README.md、requirements.txt、.env-example 的那一层,打开终端,执行:
macOS / Linux:
# 创建虚拟环境(会多出一个 .venv 文件夹);推荐使用 Python 3.10
python3.10 -m venv .venv
# 激活虚拟环境
# Windows(CMD):
.venv\Scripts\activate
# Windows(PowerShell):
.venv\Scripts\Activate.ps1
# macOS / Linux:
source .venv/bin/activate
激活成功后,终端前面通常会出现 (.venv)。
3.3 安装依赖
仍在项目根目录且虚拟环境已激活时执行:
pip install -r requirements.txt
如果网络较慢,可以换国内清华源镜像:
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
requirements.txt 覆盖的是根目录普通案例的主要依赖。电商问数等实战项目有自己的环境说明,见本文第 6 节。
4、配置 API Key 与 .env
4.1 复制 .env-example
在项目根目录执行:
cp .env-example .env
Windows 用户也可以直接手动复制 .env-example,然后重命名为 .env。
打开 .env 后,把占位 Key 改成真实 Key。真实 API Key 不要提交到 Git,本仓库已经通过 .gitignore 忽略 .env。
4.2 常见环境变量
不同章节读取的变量名不完全一样。常见变量如下:
| 变量名 | 用途 |
|---|---|
QWEN_API_KEY | 早期 HelloWorld 示例中调用通义千问 |
aliQwen-api | 多数 LangChain / LangGraph 示例中调用阿里云百炼 / 通义千问 |
deepseek-api | DeepSeek 相关示例 |
一个相对完整的 .env 示例:
QWEN_API_KEY='sk-你的通义千问Key'
aliQwen-api='sk-你的阿里云百炼Key'
deepseek-api='sk-你的DeepSeekKey'
# 天气工具示例
OPENWEATHER_API_KEY='你的OpenWeatherKey'
这里最容易出错的是变量名。aliQwen-api、deepseek-api、tavily_api_key 要和代码里的 os.getenv("...") 完全一致,大小写、横线和下划线都要对上。
注意: 上表里的变量名是本仓库代码约定,不是各厂商官方强制要求的环境变量名。官方平台通常只要求你拥有有效 API Key;放到 .env 里时,变量名必须和本地示例代码读取的名字一致。
4.3 API Key 去哪里申请
下面按「平台」说明如何拿到 API Key,用于填充 .env。
通义千问 / 阿里云百炼
- 用途:本仓库大量案例使用
aliQwen-api或QWEN_API_KEY调用通义千问。 - 申请步骤:教程中有图文说明,可直接查看 第 10 章 - LangChain 快速上手与 HelloWorld 的「2、大模型服务平台」(含阿里云百炼平台、API-Key 管理入口表格)与「4、实战:基于阿里百炼的 HelloWorld」(含 API Key 获取、模型名、Base URL 三件套)。简要步骤如下:
- 打开 阿里云官网 并登录,若没有账号需先注册。
- 搜索「百炼」或进入「灵积模型服务」相关产品页(具体名称以阿里云当前产品为准)。
- 开通「模型服务」或「百炼」后,在控制台里找到 API-KEY 管理,创建或复制 Key。
- Key 一般为
sk-开头的长字符串,复制到.env的aliQwen-api或QWEN_API_KEY中。
DeepSeek
- 用途:本仓库中
02-models_io下的 DeepSeek 示例、以及部分使用deepseek-api的脚本。 - 申请步骤:教程中有截图与步骤,见 第 10 章 - LangChain 快速上手与 HelloWorld(含「多模型共存」中 DeepSeek 的 API Key 获取与配置)。简要步骤如下:
- 打开 DeepSeek 开放平台(或搜索「DeepSeek API」)。
- 注册/登录后,在控制台或「API Key」页面创建 Key。
- 将 Key(一般为
sk-开头)填入.env的deepseek-api。
OpenAI 兼容接口
- 若案例中使用的是
openai库 +base_url方式(例如接 DeepSeek、国内兼容 OpenAI 的厂商),通常只需在.env里配置对应厂商的 Key 和文档中的base_url即可。接入方式与参数说明见 第 11 章 - Model I/O 与模型接入 的「3.1 使用 OpenAI 兼容接口」与「3.3 接入通义千问(阿里云百炼)」。 - OpenAI 官方:需在 OpenAI 平台 注册并创建 API Key,且需能访问其服务(部分地区可能需代理)。
5、常见问题速查
5.1 依赖相关
| 问题 | 常见原因 | 处理办法 |
|---|---|---|
ModuleNotFoundError: No module named 'xxx' | 没装依赖,或没有激活 .venv | 激活虚拟环境后,在项目根目录执行 pip install -r requirements.txt |
安装时报 requires-python <3.14 | Python 版本过高 | 根目录普通案例换成 Python 3.10~3.13,推荐 3.10 |
| RAG、Memory、Redis 示例跑不通 | 依赖外部服务或系统包 | 先跑 HelloWorld,再按对应章节准备 Redis、Redis Stack、PDF / Word 解析依赖 |
检查当前 Python:
macOS / Linux:
python --version
which python
pip show langchain
Windows:
python --version
where python
pip show langchain
5.2 API Key 与 .env
| 问题 | 常见原因 | 处理办法 |
|---|---|---|
| 401、403、API Key 无效 | Key 填错、平台额度不足、服务未开通 | 回平台控制台检查 Key、额度和模型服务状态 |
os.getenv() 读到空值 | .env 不存在、变量名不一致,或 IDE / 调试器的工作目录配置不合适 | 确认根目录有 .env、变量名与代码一致;使用 IDE / 调试器时再检查工作目录 |
排查 .env 时,不要把真实 Key 发到 Issue、聊天窗口或截图里。只需要说明你配置了哪些变量名即可。
5.3 运行目录与编码
| 问题 | 常见原因 | 处理办法 |
|---|---|---|
找不到 .env | .env 不存在,或 IDE / REPL / 调试器的工作目录未指向仓库内 | 确认根目录已有 .env;必要时调整工作目录,或在代码中显式传入 .env 路径 |
UnicodeDecodeError 或中文乱码 | 文件不是 UTF-8 编码 | 用 VS Code / Cursor 把 .env、.py 保存为 UTF-8 |
| 相对路径找不到文件 | 自定义代码直接使用了基于当前工作目录的相对路径 | 本仓库配套案例已按脚本目录定位资源;自定义路径建议使用绝对路径或通过 Path(__file__) 构造 |
5.4 Ollama 示例
Ollama 不需要云 API Key,但需要本机安装并启动 Ollama,还要提前拉取模型。
ollama --version
ollama list
ollama run qwen:4b
qwen:4b 是本仓库入门示例使用的轻量模型 tag。Ollama 模型和 tag 会持续更新,实际可用名称请以 Ollama 模型库 当前页面为准。
确认模型能在命令行里正常运行后,再跑 03-ollama 目录下的 LangChain 示例。
5.5 Black 格式化
本仓库用 Black 统一 Python 代码风格。
black --check .
black .
如果你使用 VS Code / Cursor,可以安装 Microsoft 的 Black Formatter 扩展,并开启保存时自动格式化。
6、遇到问题时怎么提问
如果你卡住了,可以按下面顺序自己先排查一遍:
- 当前目录是不是项目根目录。
- 虚拟环境是否已经激活。
- Python 是否在 3.10 到 3.13 之间。
- 是否执行过
pip install -r requirements.txt。 - 根目录是否有
.env。 .env变量名是否和代码一致。- 这个案例是否依赖 Redis、Ollama、Docker、Qdrant、Elasticsearch 等外部服务。
- 是否把根目录普通案例、电商问数等实战项目的环境混在了一起。
如果仍然解决不了,提 Issue 时尽量带上这些信息:
1. 你运行的命令
2. 完整报错信息
3. Python 版本
4. 操作系统
5. 你运行的是哪个章节或哪个脚本
6. 是否配置了 .env,以及配置了哪些变量名(不要贴真实 Key)
这样别人不用猜你的环境,也更容易帮你定位问题。
7、常用入口
- 完整章节导航:教程目录大纲
- 术语随查:全书术语表
- 案例汇总:教程案例链接汇总
- 工具与资料:工具导航与参考资料索引
- 面试复盘:AI 智能体与大模型应用开发面试题库
- 实战项目:电商问数
8、本项目使用的 Docsify 扩展
本仓库的在线阅读站点基于 Docsify 生成,并使用了以下扩展以提升阅读与检索体验:
| 扩展 / 配置 | 作用 |
|---|---|
| Docsify 4 + Vue 主题 | 文档框架与默认样式,负责把 Markdown 文件渲染成在线文档站点 |
| docsify-darklight-theme | 提供白天 / 夜间模式切换 |
| 侧边栏导航 | 通过 _sidebar.md 组织章节目录,页面内标题最多展示到四级标题 |
| 全文搜索(search) | 顶部搜索框,支持按关键词检索文档内容,当前搜索深度配置为 6 |
| zoom-image | 正文中的图片可点击放大查看,适合查看截图、架构图和流程图 |
| docsify-copy-code | 代码块右侧提供「复制」按钮,方便直接复制示例代码和命令 |
| docsify-pagination | 页面底部提供「上一页 / 下一页」跨章节导航 |
| docsify-count | 每页底部显示当前页字数,已按中文统计配置 |
| KaTeX + docsify-katex | 数学公式渲染,支持 Markdown 中的行内公式 $...$ 和块级公式 $$...$$ |
| Prism + prism-python | 代码语法高亮,当前额外加载了 Python 语法高亮组件 |
| Mermaid | 渲染语言标识为 mermaid 的代码块,用于流程图、架构图、时序图等可视化内容 |
| docsify-giscus | 基于 GitHub Discussions 的评论区,每页底部可以承载读者讨论 |
| Google Analytics / gtag | 站点访问统计,用于观察页面访问情况 |
| docsify-sitemap / sitemap.xml | 生成并维护 sitemap.xml,便于搜索引擎收录在线文档 |
前言
电商问数:前言
欢迎来到「电商问数」项目,正式开启「AI 智能体实战速成指南」的实战项目学习~~ ✨
如果你正在找一个真正适合入门进阶的 AI 项目,而不是只调用一次大模型接口、写几个 Prompt、演示一下 SQL 生成结果,那么这套「电商问数」教程非常适合你。
它把 MySQL、LangGraph、Qdrant、Elasticsearch、Embedding、FastAPI、SSE 和前端联调放进同一条业务链路里,用一个自然语言问数场景,把智能体项目开发中最关键的知识点串起来:
- 用
MySQL模拟教学数仓和元数据库 - 用
Qdrant做字段和指标的语义向量检索 - 用
Elasticsearch做字段真实取值的全文检索 - 用
LangGraph编排多阶段问数智能体工作流 - 用
FastAPI + SSE把后端能力交付给前端页面

一句话概括:「电商问数」不是一个只会让大模型裸写 SQL 的 Demo,而是一套围绕“先构建元数据知识库,再检索、推理、生成、校验和执行 SQL”展开的智能问数实战项目。
它可能不是一个完整的企业级生产系统,但非常适合作为入门到进阶阶段的项目实战课程。你可以在这里系统学习一套智能体应用从“数据准备”到“检索增强”,再到“工作流编排”和“接口交付”的完整主链路。
配套源码仓库地址:https://github.com/didilili/shopkeeper-agent
1、为什么说它适合入门进阶
很多 AI 项目教程容易走向两个极端:
- 要么太轻,只演示一次模型调用,学完之后不知道怎么做真实项目
- 要么太重,一上来就塞进大量生产级能力,新手很容易迷失在工程复杂度里
「电商问数」选择的是中间更适合学习的一条路:保留真实问数系统最核心、最必要的工程链路,但不一开始追求大而全。
你会看到一个智能问数项目为什么不能只靠大模型直接生成 SQL,而要先解决下面这些问题:
- 面对多张事实表和维度表,系统怎么知道该查哪些表
- 用户说“华北”“黄金会员”“苹果品牌”时,系统怎么定位到真实字段和值
- 字段、指标、字段取值为什么要分开建索引
- 元数据库、向量索引、全文索引分别负责什么
- 召回出来的信息为什么不能直接丢给模型,还要合并、过滤和补全
- SQL 生成后为什么还要校验、纠错,再执行
- 后端接口为什么要用流式返回,让前端看到执行过程
这也是它非常适合学习 MySQL、LangGraph、Qdrant 等技术的原因:这些技术不是孤立出现的,而是都被放进了一个清楚的业务目标里。
你不是单独学一个数据库、一个向量库、一个框架 API,而是在学习它们如何配合完成一个真实的智能体应用。
2、这套课程的另一个亮点
这套实战项目不只是“最终代码 + 一篇说明文档”。它的每个关键章节,都配有对应的代码分支。
也就是说,你不用一上来面对完整项目的最终形态,而是可以按章节一步一步切换代码:
- 第 3 章先准备环境和基础服务
- 第 4 章理解项目结构和配置管理
- 第 5、6 章接入 Qdrant、Elasticsearch、MySQL、Embedding 和日志
- 第 7 到 9 章构建元数据知识库和检索能力
- 第 10 到 14 章逐步搭建 LangGraph 问数智能体
- 第 15 到 17 章把能力通过 FastAPI、SSE 和前端页面交付出去
每个阶段都有明确的学习目标、代码实现和知识讲解。你可以先看章节理解设计,再切换到对应分支对照代码,也可以先跑代码,再回到文档理解为什么这么写。
这对入门进阶非常友好。因为真正难的不是“看懂最终项目里有哪些文件”,而是知道这些文件是怎么一步步长出来的,为什么要这样拆层,为什么这一章只做这一部分,下一章又在前一章基础上补了什么能力。
如果你想做一个后续能写进简历、面试里也聊得起来的 AI 项目,「电商问数」会比单纯的模型调用 Demo 更有代表性。它不只是展示效果,而是能让你讲清楚:数据层、检索层、智能体层、服务层和前端交付层分别做了什么。
3、这个项目到底在做什么
在真实企业里,业务同学通常不会写 SQL,数据分析同学也不可能随时记住所有表结构、字段含义和指标口径。于是就会出现一个非常典型的需求:
- 能不能直接用自然语言提问?
- 系统能不能自动找到相关表、字段和指标?
- 能不能自动生成 SQL,并返回可以真正拿来分析的结果?
「电商问数」要解决的,就是这个问题。
下面这张图可以先帮助你建立整体印象:用户提出自然语言问题后,系统会经过后端调度、字段和表结构召回、自然语言转 SQL、数据库查询、SSE 流式推送,最后把结果展示给用户。

从使用者视角看,它就是一个“会查数的智能数据分析助手”:
- 用户先用自然语言提问
- 系统理解问题,并抽取适合检索的关键信息
- 再到元数据知识库中召回相关表、字段、指标和字段取值
- 把这些信息整理成更适合大模型理解的上下文
- 再由大模型生成 SQL,并做必要的校验和纠错
- 最后到数据仓库执行 SQL,并把过程和结果返回给前端
当一次查询真正跑起来时,前端不只是等待最后结果,而是可以看到 LangGraph 工作流的执行过程:抽取关键词、召回字段信息、召回指标信息、召回字段取值、合并召回信息、过滤上下文、生成 SQL、校验 SQL、执行 SQL,最后展示查询结果。

所以,这个项目的重点从来不只是“大模型”,而是下面这整条链路:
- 数据仓库怎么设计和模拟
- 元数据知识库怎么构建
- 字段、指标、字段取值怎么做混合检索
- 智能体怎么基于上下文生成更可靠的 SQL
- 后端服务怎么把智能体能力交付给前端
- 日志和异常怎么帮助排查一条完整请求链路
后面的章节都会围绕这条主线展开:先把数仓里的表、字段、指标和字段取值整理成元数据知识库,再让问数智能体基于这套知识库完成检索、推理、SQL 生成、校验、执行和结果返回。
4、适合学什么,也不刻意覆盖什么
这套项目非常适合学习下面这些内容:
| 学习方向 | 在项目里的体现 |
|---|---|
MySQL | 模拟教学数仓、保存元数据库、执行最终 SQL 查询 |
| 数仓基础 | 理解事实表、维度表、指标、字段角色和分析型查询 |
| 元数据建模 | 构建 table_info、column_info、metric_info、column_metric 等结构化元数据 |
Qdrant | 保存字段和指标向量,用于语义召回 |
Elasticsearch | 保存字段真实取值,用于关键词和值域检索 |
Embedding | 将字段、指标、问题等文本转成向量 |
LangGraph | 编排关键词抽取、并行召回、上下文合并、SQL 生成、校验和执行 |
FastAPI | 提供查询接口,组织依赖注入和生命周期管理 |
SSE | 将问数过程、执行结果和异常信息实时返回前端 |
| 工程分层 | 学习配置、客户端、仓储层、服务层、智能体节点和接口层如何协作 |
同时也要把边界说清楚:
「电商问数」适合入门到进阶阶段学习,不是完整企业级生产系统。
它重点覆盖问数系统最核心的主链路:教学数仓、元数据知识库、混合检索、LangGraph 工作流、SQL 生成校验执行、FastAPI 接口、SSE 返回、前后端联调和日志追踪。
但它不会在第一个项目里展开所有生产级能力,比如:
- 用户权限和数据权限控制
- 多租户隔离
- 复杂 SQL 安全审计
- 查询缓存和性能治理
- 完整评测体系
- 监控告警和链路追踪平台
- 灰度发布和生产部署治理
- 更复杂的多轮问数记忆系统
这些能力当然重要,但如果全部放进第一个实战项目里,学习成本会非常高,主线反而容易被淹没。
所以这套项目更适合承担一个清晰角色:先帮你把智能问数项目最关键、最必要、最值得学习的主链路跑通,并且讲透。
5、这套项目的核心技术栈
为了把这条链路真正落成一个可运行的项目,当前这套实战项目主要使用了下面这些技术:
| 模块 | 技术栈 | 作用说明 |
|---|---|---|
| 教学数仓 | MySQL | 在教学环境中模拟数据仓库,保存事实表和维度表 |
| 元数据库 | MySQL | 保存结构化元数据,如 table_info、column_info、metric_info、column_metric |
| 向量数据库 | Qdrant | 保存字段和指标的语义向量索引 |
| 全文检索 | Elasticsearch | 保存部分字段真实取值,支持关键词和值域检索 |
| 向量化服务 | TEI + BAAI/bge-large-zh-v1.5 | 负责文本向量化 |
| 智能体编排 | LangGraph | 组织问数智能体的多阶段工作流 |
| 大模型调用 | LangChain | 封装模型调用与部分链路能力 |
| 后端接口 | FastAPI | 提供后端服务与接口能力 |
| ORM 与数据访问 | SQLAlchemy | 管理元数据库和数据仓库相关访问 |
| 流式返回 | SSE | 将查询过程与结果实时返回前端 |
| 中文分词 | Jieba | 支撑部分文本预处理和关键词处理 |
| 环境与依赖管理 | uv | 管理 Python 项目环境和依赖 |
| 前端项目 | React + Vite + Tailwind CSS | 承载问数前端页面与交互展示 |
后端用 FastAPI + LangGraph 组织问数流程,用 MySQL + Qdrant + Elasticsearch 搭建元数据知识库,再由大模型完成 SQL 生成、校验和纠错,最后通过 SSE 把执行过程和结果返回给前端。
6、项目目录结构
shopkeeper-agent/
├── app/ # 后端源码主目录
│ ├── agent/ # 问数智能体与 LangGraph 图流程
│ │ └── nodes/ # 关键词抽取、召回、过滤、SQL 生成、校验、执行等节点
│ ├── api/ # 对外 HTTP 接口层,对应 FastAPI 路由、依赖注入和请求参数结构
│ ├── clients/ # 各类基础服务客户端,例如 MySQL、ES、Qdrant、Embedding 服务
│ ├── conf/ # 配置类与配置加载工具,把 YAML 配置转换成代码可直接使用的对象
│ ├── core/ # 通用基础能力,例如日志、生命周期管理、请求上下文等
│ ├── entities/ # 业务实体定义,承接比 ORM 更贴近业务含义的数据结构
│ ├── models/ # ORM 模型,主要对应 MySQL 中的表结构
│ ├── prompt/ # 提示词加载工具,负责读取和组织静态 Prompt 资源
│ ├── repositories/ # 数据访问层,封装 MySQL / Qdrant / ES 的具体读写逻辑
│ ├── scripts/ # 工具脚本,例如构建元数据知识库、初始化或同步数据
│ └── services/ # 业务逻辑层,负责把客户端、仓储层和智能体能力真正串起来
├── conf/ # 项目级 YAML 配置文件,例如数据库、向量库、ES、LLM、日志等配置
├── docker/ # 本地开发环境相关文件,例如 docker-compose 和自定义镜像资源
│ ├── elasticsearch/ # Elasticsearch 相关文件,例如 Dockerfile、插件或初始化资源
│ ├── embedding/ # Embedding 服务相关文件,例如模型目录或推理服务配置
│ └── mysql/ # MySQL 初始化 SQL、建表脚本或测试数据导入文件
├── frontend/ # React + Vite + Tailwind CSS 前端项目
├── logs/ # 本地运行时日志输出目录
└── prompts/ # 静态提示词文件,和 app/prompt 中的加载工具配合使用
如果你已经准备好了,就从下一章开始,正式进入「电商问数」的项目学习。
后面的内容会按照 “项目概述与数仓基础 -> 整体架构与智能体流程 -> 环境准备 -> 元数据知识库 -> 问数链路实现 -> API 接口与前后端联调” 这条主线,逐步把整套项目拆开讲清楚。
学完之后,你收获的不只是“我跑通了一个 AI Demo”,而是能真正讲清楚:一个智能问数系统为什么要这样设计,代码为什么要这样拆,MySQL、Qdrant、Elasticsearch、LangGraph 和 FastAPI 在一条完整业务链路里分别解决什么问题。

前言
深度研搜:前言
欢迎来到「深度研搜」项目,正式进入「AI 智能体实战速成指南」里更偏多智能体、长任务和工程闭环的一套实战项目。
如果你已经学过大模型接口调用、Prompt、RAG、LangChain 或 LangGraph,下一步通常会遇到一个更实际的问题:复杂任务到底怎么组织成一个能跑、能看、能交付的项目?
「深度研搜」就是围绕这个问题设计的。它不是再写一个聊天框,也不是只接一个搜索 API,而是用 DeepAgents 搭一个对话式多智能体研究台:用户输入研究任务或上传附件,主智能体判断任务需要哪些信息,再调度网络搜索、数据库查询、RAGFlow 知识库等能力,最后整理回答,或生成 Markdown / PDF 文件。

「深度研搜」不是一个只让大模型回答问题的 Demo,而是一套围绕“主智能体规划调度、子智能体分工执行、多来源资料检索、文件生成交付、前后端实时联动”展开的 DeepAgents 多智能体实战项目。
它可能还不是一个完整的企业级生产系统,但非常适合作为入门到进阶阶段的项目实战课程。你可以在这里系统学习一个多智能体应用如何从概念、示例、子智能体、长期记忆、中间件,一步步走到真实的 FastAPI 接口和前端页面。
配套源码仓库地址:https://github.com/didilili/deepsearch-agents
1、这套项目重点学什么
很多入门项目只做到“模型能回答”。这当然重要,但离一个可交付的智能体应用还差几步。
在「深度研搜」里,重点不是让模型说得更长,而是解决这些更具体的问题:
- 一个任务什么时候该查网络,什么时候该查数据库,什么时候该查知识库;
- 子智能体的
description、system_prompt、tools应该怎样分工; - 上传文件和生成文件怎样按会话隔离,避免不同任务互相覆盖;
- 长任务为什么不能让
HTTP请求一直等,为什么要用WebSocket回传进度; - 工具在深层调用中怎样拿到当前
thread_id和session_dir; - 最终结果怎样从命令行示例走到前端页面、输出文件和下载入口。
所以,这套项目更像一条工程练习线:从 DeepAgents 的基本能力开始,逐步把子智能体、记忆、中间件、文件工具、后端接口和前端联调串起来。
学完之后,你应该能明白三件事:
DeepAgents适合处理哪类长任务,和普通工具型Agent有什么区别。- 主智能体、子智能体、工具、上下文、文件系统之间应该怎样分层。
- 一个多智能体能力怎样通过
FastAPI、WebSocket和前端页面真正交付出去。
2、这个项目最终做成什么样
从用户视角看,它是一个“深度研究助手”。
用户可以提出这样的任务:
结合公开资料、数据库信息和我上传的文档,整理一份电商行业研究报告,并生成 PDF。
系统背后会按需做这些事:
- 判断任务需要哪些信息来源;
- 用
Tavily查询公开网络资料; - 用
MySQL查询结构化数据; - 用
RAGFlow查询内部知识库; - 读取用户本次上传的
PDF、Word、Excel或Markdown文件; - 汇总资料,判断信息是否足够;
- 生成
Markdown,必要时再转换成PDF; - 把执行过程、最终结果和输出文件展示给前端。
执行过程中,前端不会只显示“等待中”。它会展示 WebSocket 状态、助手调度次数、工具调用次数,以及每一次 tool_start、assistant_call、task_result 等事件。

如果任务生成了文件,页面还会展示输出文件列表。下面这个例子里,数据库查询助手执行 SQL 后,主智能体继续调用 Markdown 文档生成工具,最后把 .md 文件放到当前会话的输出区域,用户可以直接下载。

3、章节怎么安排
这套项目分成两段。
第一段是 DeepAgents 能力铺垫,主要对应第 1 到 7 章:
| 章节 | 重点 |
|---|---|
| 第 1 章 | 建立 DeepAgents 的定位:长任务、上下文、子智能体、长期记忆 |
| 第 2 章 | 跑通最小示例,理解 invoke()、stream() 和流式 chunk |
| 第 3 章 | 学会字典式子智能体,以及主智能体如何调度它们 |
| 第 4 章 | 接入已有 LangGraph 图、LangChain 的 Agent 或 Runnable |
| 第 5 章 | 理解人机协作、中断、审批、编辑和恢复执行 |
| 第 6 章 | 理解 Backend、文件系统、Store 和长期记忆 |
| 第 7 章 | 理解中间件和 Skills,给 Agent 执行链路加治理能力 |
第二段是「深度研搜」项目主线,主要对应第 8 到 14 章:
| 章节 | 重点 |
|---|---|
| 第 8 章 | 看清最终项目目标、整体架构和工程目录 |
| 第 9 章 | 搭好 .env、context.py、monitor.py、llm.py、prompts.yml 等底座 |
| 第 10 章 | 实现网络搜索子智能体,接入 Tavily |
| 第 11 章 | 实现数据库查询子智能体,接入 MySQL |
| 第 12 章 | 准备 RAGFlow 知识库,并封装知识库助手 |
| 第 13 章 | 组装主智能体,接入上传文件读取、Markdown 生成、PDF 转换 |
| 第 14 章 | 用 FastAPI、WebSocket 和前端页面完成闭环 |
这样安排的好处是,前面先把 DeepAgents 的核心能力拆开讲,后面再把这些能力放回一个真实项目里。你不会一开始就被完整项目淹没,也不会只停留在零散示例。
4、技术栈和学习重点
当前项目用到的技术并不多,但每个技术都有明确位置。
| 模块 | 技术栈 | 在项目里的作用 |
|---|---|---|
| 智能体框架 | DeepAgents | 创建主智能体和子智能体,承接长任务和分工调度 |
| 底层运行时 | LangGraph | 提供检查点、状态恢复和与图工作流的兼容能力 |
| 模型与工具 | LangChain | 提供模型封装、工具声明和 Runnable / Agent 兼容方式 |
| 网络搜索 | Tavily | 给网络搜索助手提供公开资料检索能力 |
| 结构化数据 | MySQL | 给数据库查询助手提供表名、样例数据和 SQL 查询能力 |
| 私有知识库 | RAGFlow | 给知识库助手提供内部文档问答能力 |
| 文件交付 | Markdown / PDF / Word / Excel 解析 | 读取上传附件,生成 Markdown,并转换成 PDF |
| 后端接口 | FastAPI | 提供任务启动、取消、上传、文件列表、下载和 WebSocket 接口 |
| 实时通信 | WebSocket | 把执行进度、结果和异常实时推给前端 |
| 前端页面 | React + Vite + Ant Design | 展示会话、任务事件、输出文件和下载入口 |
| 环境管理 | uv | 管理 Python 环境和依赖 |
你不需要把每个技术都学到很深才开始。更好的方式是先看它在链路里解决什么问题,再回到对应章节看实现细节。
5、这套项目不刻意覆盖什么
「深度研搜」适合入门到进阶阶段学习,但它不是完整企业级生产系统。
当前版本重点覆盖的是主链路:
多智能体分工
-> 多来源工具接入
-> 会话上下文隔离
-> 文件生成交付
-> FastAPI 接口
-> WebSocket 实时进度
-> 前端闭环展示
有些生产级能力没有展开,比如:
- 用户登录和数据权限;
- 多租户隔离;
- 文件上传安全扫描;
- 分布式任务队列;
- 大规模并发与限流;
- 全量事件持久化;
- 系统评测与回归测试集;
- 监控告警和链路追踪;
- 在线报告编辑与协作。
这些能力都重要,但不适合一开始全部放进来。第一版先把主链路讲清楚,后面再逐层扩展,会更容易学,也更容易改。
6、项目目录结构
配套源码仓库是 deepsearch-agents。最终项目的主要结构如下:
deepsearch-agents/
├── app/ # 后端源码主目录
│ ├── agent/ # 主智能体、模型配置、提示词加载和子智能体配置
│ │ ├── subagents/ # 网络搜索助手、数据库查询助手、RAGFlow 知识库助手
│ │ ├── llm.py # 大模型初始化
│ │ ├── main_agent.py # 主智能体组装与 run_deep_agent 执行入口
│ │ └── prompts.py # 读取 prompts.yml 中的主智能体和子智能体配置
│ ├── api/ # FastAPI 接口层、上下文和实时进度推送
│ │ ├── context.py # ContextVar 保存 thread_id 和 session_dir
│ │ ├── monitor.py # 统一封装工具调用、助手调用和任务结果事件
│ │ └── server.py # 任务、取消、上传、文件列表、下载和 WebSocket 接口
│ ├── prompt/ # prompts.yml,集中管理主智能体和子智能体提示词
│ ├── ragflow/ # RAGFlow 连接配置和基础调用示例
│ ├── tools/ # Tavily、MySQL、RAGFlow、文件读取、Markdown、PDF 工具
│ ├── utils/ # 路径解析、Word/PDF 转换等通用工具
│ ├── output/ # 每个会话生成的 Markdown、PDF 和输出文件
│ └── updated/ # 用户上传文件的会话暂存目录
├── docker/ # 本地 MySQL 等服务的 Docker Compose 配置
├── examples/ # 1 到 15 章对应的 DeepAgents 示例脚本
├── frontend/ # React + Vite 前端项目
├── tests/ # 工具、连接管理、取消任务等测试
├── pyproject.toml # Python 项目配置
├── requirements.txt # 依赖清单
└── uv.lock # uv 锁定文件
7、建议怎么学
如果你是第一次接触 DeepAgents,不建议直接从最终版的主智能体 main_agent.py 开始看。它背后依赖了子智能体、检查点、ContextVar、monitor、文件工具、异步流式执行和 WebSocket 推送。
更稳的顺序是:
先看第 1 章,知道 DeepAgents 解决什么问题
-> 跑第 2 章,熟悉 invoke / stream / chunk
-> 看第 3 章,掌握子智能体配置和调度
-> 看第 5 到 7 章,理解中断、记忆、中间件和 Skills
-> 从第 8 章进入真实项目
-> 第 9 章看懂 context、monitor、path_utils、llm、prompts
-> 第 10 到 12 章完成三个专家助手
-> 第 13 章组装主智能体和文件交付工具
-> 第 14 章接上 FastAPI、WebSocket 和前端页面
如果你已经准备好了,就从下一章开始,正式进入「深度研搜」的项目学习。
后面的内容会按照 “DeepAgents 核心概念 -> 快速入门与流式解析 -> 子智能体与兼容接入 -> 人机协作、记忆、中间件 -> 项目工程初始化 -> 三个专家助手 -> 主智能体 -> FastAPI 与前端闭环” 这条主线,逐步把整套项目拆开讲清楚。
学完之后,你收获的不只是“跑通了一个 AI Demo”,而是能说清楚:一个 DeepAgents 多智能体系统为什么要这样设计,代码为什么要这样拆,DeepAgents、WebSocket、Tavily、MySQL 和 RAGFlow 在一条完整业务链路里分别解决什么问题。
DeepAgents基础与核心概念
1 - 深度研搜:DeepAgents 基础与核心概念
本章课程目标:
- 理解为什么在普通大模型调用、工具型 Agent 之后,还需要 DeepAgents 这类深度智能体框架。
- 掌握
LangChain、LangGraph、DeepAgents三者之间的定位差异。 - 理解 DeepAgents 的四个核心能力:任务规划、上下文管理、子智能体、长期记忆。
- 理解高阶提示词为什么会和 DeepAgents 一起出现,以及它在复杂任务中的作用。
- 建立多智能体设计的边界感:知道什么时候适合使用子智能体,什么时候不应该滥用多智能体。
学习建议: 这一章先建立边界,不急着写代码。重点看普通 Agent 为什么处理长任务会吃力,DeepAgents 把规划、文件、子智能体、长期上下文这些能力放在什么位置。读完后最好能回答:什么任务值得交给 DeepAgents,什么任务用普通 Agent 或一条链就够了。下一章再进入 create_deep_agent()、invoke()、stream()。
1、项目导读
1.1 项目背景
「深度研搜(DeepAgents 多智能体)」是一个面向复杂研究任务的企业级智能体实战项目。它最终要做的不是简单问答,而是让智能体围绕一个开放问题进行资料搜索、信息整理、反思补充和报告生成。
如果把后续项目目标说得更具体一点,可以理解为一个「深度研搜」系统:
- 用户提出一个研究问题;
- 系统自动拆解研究方向;
- 不同工具或子智能体分别完成搜索、阅读、分析、校验等任务;
- 主智能体汇总中间结果,生成一份结构完整的研究报告。
这类任务和普通聊天问答不一样。它通常没有固定答案,也不一定能提前写死完整流程。系统需要一边执行,一边根据中间结果调整后续动作。
这正是 DeepAgents 适合发挥作用的地方。
1.2 为什么需要 DeepAgents
前面我们已经学习过不少大模型应用开发方式,例如:
- 直接调用大模型接口,让模型返回文本;
- 使用 LangChain 封装模型、提示词、输出解析器和工具;
- 使用 LangGraph 把复杂任务拆成多个固定节点;
- 使用 Agent 让模型在执行过程中自主决定是否调用工具。
这些方式都很重要,也能完成很多任务。但当任务进一步变长、变开放、变复杂时,普通 Agent 会逐渐遇到几个问题:
- 上下文越来越乱,工具返回结果和历史消息容易挤在一起;
- 任务拆解不稳定,模型可能想到哪做到哪;
- 所有事情都压在一个主智能体身上,专业分工不清晰;
- 中间过程难以观察,出错时不好定位;
- 多轮搜索、多轮反思、多方向并行时,执行链路容易失控。
比如用户让智能体完成一份“人工智能与机器人行业热点新闻研究报告”。这件事至少包括理解问题、联网搜索、筛选资料、总结观点、组织报告等步骤。如果继续扩展成深度搜索,还可能需要补充搜索、交叉验证、反思遗漏信息,甚至让不同子智能体分别处理不同方向。
DeepAgents 要解决的,正是这类更复杂、更长链路、更需要自主规划的智能体任务。
简单来说:
DeepAgents(深度代理) 不是只让模型回答问题,而是让智能体像一个会规划、会分工、会使用工具、会管理上下文的任务负责人。
2、智能体演进:从 LLM 到 DeepAgents
2.1 三个发展阶段
可以先用一张图建立整体印象。

这张图表达的是大模型应用能力的递进关系:最早的大模型主要负责语言生成;进入 Agent 阶段后,模型开始能调用工具、执行动作;再往后发展到 Agentic AI,系统开始具备更强的规划、协作和长期任务推进能力。
如果拆成学习路径,可以分成三个阶段。
第一阶段:直接调用大模型
最早的大模型应用通常很简单:把提示词发给模型,模型返回文本。
用户问题 -> Prompt -> LLM -> 文本结果
这种方式适合问答、摘要、改写、分类等相对简单的任务。它的优势是简单直接,但缺点也明显:模型只能基于已有上下文回答,不能主动访问外部系统,也不能真正执行动作。
比如你问它“今天有哪些人工智能新闻”,如果没有联网工具,它只能基于训练知识或上下文猜测,无法拿到实时信息。
第二阶段:工具型 Agent
后来我们开始给模型配工具,例如搜索工具、数据库工具、计算工具、文件工具。Agent 的核心变化是:模型不再只是回答问题,它可以根据任务判断是否调用工具。
用户问题 -> Agent -> LLM 判断 -> 调用工具 -> 工具结果 -> LLM 总结 -> 最终结果
这时的 Agent 已经具备了“行动能力”。它可以先让模型判断下一步该干什么,再调用对应工具,最后把工具结果整理成自然语言。
但普通 Agent 通常还是一个主智能体在处理所有事情。随着任务变长,所有工具说明、历史消息、工具返回结果都会挤进同一个上下文窗口,主智能体既要规划,又要搜索,又要分析,还要写报告,压力会越来越大。
第三阶段:DeepAgents 多智能体
DeepAgents 在普通 Agent 的基础上进一步增强:除了调用工具,还可以调用子智能体。
用户任务
-> 主智能体负责规划和调度
-> 调用工具或分派给子智能体
-> 子智能体独立完成局部任务
-> 主智能体汇总并输出最终结果
这里的关键变化是:主智能体不再什么都亲自干,而是更像一个项目负责人。它可以把不同任务交给不同子智能体,让子智能体在各自的上下文中完成专业任务,再把结果汇总回来。
这就是 DeepAgents 名字里 “Deep” 的含义:它面向的是更深层、更长链路、更复杂的智能体执行过程。
2.2 固定工作流与自主智能体
理解 DeepAgents 之前,还需要区分一个概念:固定工作流和 Agent 自主决策不是一回事。
在 LangGraph 项目里,我们经常会写一个固定的图。例如:
START -> extract_keywords -> recall_info -> generate_sql -> validate_sql -> END
这种图的特点是:节点和边基本由开发者提前写好。运行时可以有条件分支,但整体路线是清晰、可控、固定的。
Agent 的执行方式更灵活。它会根据用户问题、系统提示词、工具描述、子智能体描述,动态决定下一步做什么。它可能先调搜索工具,也可能先写待办清单,也可能把任务分派给某个子智能体。
可以用下面这张表快速对比:
| 形态 | 谁决定流程 | 适合场景 |
|---|---|---|
| 普通 LLM 调用 | 开发者提前写好 Prompt | 简单问答、摘要、分类 |
| LangChain Agent | 模型根据工具描述动态决策 | 简单工具调用型任务 |
| LangGraph | 开发者显式编排节点和边 | 流程明确、需要强控制的任务 |
| DeepAgents | 主智能体基于提示词、工具、子智能体自主规划 | 长链路、复杂、多步骤、多角色任务 |
这张表里最容易混淆的是 LangGraph 和 DeepAgents。
如果你已经明确知道“第一步做什么、第二步做什么、失败后走哪里”,LangGraph 很合适;如果任务本身比较开放,执行路线需要智能体自己规划和调整,DeepAgents 更合适。
2.3 Deep Agents 与高阶提示词
在深度搜索项目里,除了 Deep Agents(深度代理),还会反复遇到一个概念:高阶提示词。
这两个概念最好放在一起理解。
Deep Agents 解决的是“任务怎么组织”的问题。面对复杂任务时,智能体不再只做一次性回答,而是要具备下面这些能力:
- 先把复杂任务拆成可执行的子目标;
- 为不同子目标匹配工具或子智能体;
- 执行过程中观察中间结果,发现缺口后继续补充;
- 根据工具返回和子任务结果调整计划;
- 出现偏差时,通过反思和修正让任务继续向目标收敛。
高阶提示词解决的是“模型应该怎么思考”的问题。
普通提示词通常告诉模型“做什么”,例如:
请分析这条用户评论的情绪,并给出改进建议。
高阶提示词会进一步告诉模型“按什么步骤思考”,例如:
请按以下流程分析:
1. 先提取评论中出现的具体问题;
2. 再判断情绪倾向,并给出理由;
3. 按影响程度对问题排序;
4. 每个问题给出可落地的改进建议;
5. 最后用一句话总结核心痛点。
可以把两者的关系理解成:
- Deep Agents 是智能体系统的组织架构,决定任务如何规划、分工、执行和反馈;
- 高阶提示词是智能体系统的思考规范,决定模型如何分析问题、拆解逻辑和组织输出。
所以后续做深度搜索项目时,不能只关注“有没有工具”和“有没有子智能体”。真正决定系统稳定性的,往往是这两件事能不能配合好:用 DeepAgents 组织执行链路,用高阶提示词约束推理过程。
3、DeepAgents 框架定位
3.1 与 LangChain、LangGraph 的关系
DeepAgents 是 LangChain 生态中的一个独立库,建立在 LangChain 和 LangGraph 的基础能力之上,用来构建更复杂的自主多智能体系统。
它的目标不是替代 LangChain 或 LangGraph,而是在它们之上补齐更高层的智能体能力:
- 内置任务规划能力;
- 内置上下文管理和文件系统能力;
- 支持子智能体分工;
- 支持长期记忆;
- 支持复杂任务的持续执行和流式观测。

这张图可以从内到外看:
- 最内层是
LangChain,负责大模型与工具之间的基本交互; - 中间层是
LangGraph,负责把执行过程组织成有状态、可持久化、可流式输出的图; - 最外层是
DeepAgents,进一步提供规划工具、文件系统工具、子智能体和后端存储能力。
也就是说,DeepAgents 并不是重新发明一套模型调用框架,而是在已有的模型、工具和图执行能力之上,继续补齐“复杂任务如何长期组织”的能力。
3.2 三类框架使用场景对比
这三个框架都在 LangChain 生态里,但使用场景并不完全一样。
| 框架 | 核心定位 | 更适合什么时候使用 |
|---|---|---|
LangChain | 基础开发框架,封装模型、Prompt、工具、Agent 等能力 | 快速构建简单 Agent,或者自己组合模型与工具 |
LangGraph | 图执行框架,强调状态、节点、边、分支、持久化和流式输出 | 流程明确、需要细粒度控制、需要稳定编排的复杂工作流 |
DeepAgents | 深度智能体框架,强调自主规划、子智能体、上下文管理和长期记忆 | 长任务、多步骤、多角色、需要智能体自己规划和调度的任务 |
可以再换一种更工程化的说法:
- 如果你只是想让模型调用几个工具,
LangChain Agent就够了。 - 如果你已经知道业务流程每一步怎么走,并希望强控制每个节点,优先用
LangGraph。 - 如果任务非常开放,步骤不固定,还需要智能体自己拆任务、调工具、分配子任务,就适合使用
DeepAgents。
本项目最终要做的是一个“深度搜索”类应用:用户给出一个开放问题,系统需要搜索资料、阅读信息、反思缺口、继续补充、最终生成报告。这类任务很难提前把每一步完全写死,因此非常适合作为 DeepAgents 的实战项目。

可以从左到右理解这三层:
Framework层更关注基础抽象和集成,重点是让开发者更快接入模型、工具和 Agent;Runtime层更关注稳定执行,重点是持久化、流式输出、人机交互和状态管理;Harness层更关注复杂任务组织,重点是预定义工具、提示词、子智能体和更强的自主性。
对应到本项目里,可以这样理解:
LangChain更像“基础组件库”,提供模型、工具、提示词、Agent 等基础能力;LangGraph更像“流程运行时”,负责把多步骤任务组织成有状态、可恢复、可流式观察的执行过程;DeepAgents更像“任务组织套件”,在前两者之上进一步提供规划、文件系统、子智能体和长期记忆等能力。
这也是为什么 DeepAgents 会出现在更高一层:它面对的不是“怎么调一次模型”,而是“怎么让一个智能体系统持续推进复杂任务”。如果说 LangChain 帮我们把工具接起来,LangGraph 帮我们把流程管起来,那么 DeepAgents 更关注的是让智能体自己学会“拆任务、管资料、分派子任务、汇总结果”。
4、DeepAgents 核心能力
4.1 任务规划与任务分解
复杂任务最怕一上来就直接执行。
比如用户要求:“帮我调研一下最近机器人产业的发展,并写一份报告。”普通模型可能马上开始写内容,但这份内容很可能只是凭感觉拼接,没有清晰的资料来源、分析结构和补充机制。
DeepAgents 更推荐的方式是:先规划,再执行。
它内部提供了类似 write_todos、read_todos、update_todos 这样的待办清单能力,让智能体可以把复杂任务拆成多个步骤。例如:
1. 明确研究主题和范围
2. 搜索近期机器人产业新闻
3. 筛选高价值信息来源
4. 总结产业趋势
5. 补充典型企业和案例
6. 生成最终研究报告
这样做的好处是,智能体不再只是“想到哪写到哪”,而是有了一个可以被更新、被追踪、被检查的任务计划。
4.2 上下文管理与文件系统
大模型上下文窗口是有限的。工具返回内容越多,历史消息越长,模型越容易被无关信息干扰。
DeepAgents 内置了文件系统相关能力,例如:
ls:查看当前有哪些文件;read_file:读取文件内容;write_file:写入文件;edit_file:修改文件。
这套能力不是为了让智能体随便操作本地文件,而是为了给智能体一个外部工作区。它可以把长资料、阶段性笔记、中间报告写到文件里,主上下文里只保留当前最需要的信息。
可以把它理解成:模型的上下文只放当前正在思考的内容,文件系统负责保存大量中间资料。
4.3 子智能体与上下文隔离
单个智能体的能力再强,也会受到上下文、工具权限、专业提示词的限制。
多智能体的核心思路是“分而治之”:
- 主智能体负责理解用户目标、拆解任务、调度资源、汇总结果;
- 子智能体负责某一类专业任务,例如搜索、数据库查询、代码审查、报告撰写;
- 每个子智能体有自己的系统提示词、工具集合和执行上下文。

这张图里最关键的点是:主智能体并不直接吞下所有上下文,而是通过 task 工具把工作委派出去。子智能体在独立上下文中完成搜索、代码、通用任务等细节工作,最后只把可汇总结果交回主智能体。
这样做能明显缓解上下文膨胀问题。尤其是当工具输出很大时,例如网页搜索、文件读取、数据库查询,如果全部塞给主智能体,主智能体很快就会被中间过程淹没。子智能体机制的意义,就是让细节工作发生在局部上下文里,让主智能体保留全局视角。
4.4 长期记忆
普通智能体经常有一个问题:当前会话结束后,它就不记得之前做过什么了。
但很多企业级任务不是一次性问答,而是持续推进的。例如:
- 长期跟进某个客户需求;
- 多次迭代同一个研究报告;
- 持续维护一个项目文档;
- 在不同会话中复用之前沉淀的经验。
DeepAgents 可以借助 LangGraph 的 Store 等能力,把关键记忆保存到外部存储中,让智能体跨会话读取历史信息。
这一章只需要先理解长期记忆的价值:让智能体不只是当前会话里的助手,而是可以持续积累上下文的任务伙伴。
具体的后端存储、文件系统后端、StoreBackend、StateBackend 等内容,会在后续进阶章节展开。
4.5 四个核心能力小结
把上面的内容收束一下,DeepAgents 最核心的是下面四类能力:
| 核心能力 | 解决什么问题 | 可以怎么理解 |
|---|---|---|
| 智能规划与任务分解 | 复杂任务不知道从哪开始、做完哪些步骤才算完成 | 先拆任务、再执行、再更新进度 |
| 高效上下文管理 | 搜索结果、文件内容、工具返回值太大,容易塞爆上下文 | 把大块信息写到文件系统,需要时再读回来 |
| 子智能体机制 | 主智能体既要规划又要干活,专业性和上下文都会被稀释 | 主智能体负责调度,子智能体负责专业子任务 |
| 长期记忆 | 多轮会话之间容易失忆,历史经验无法复用 | 把关键记忆保存下来,后续会话可继续读取 |
本章只建立概念。下一章会真正写到工具调用、非流式执行和流式解析,并在流式输出中初步识别 task 子智能体调用。至于子智能体如何配置、什么时候拆分、如何异步并发,会放到第 3 章系统展开。文件系统、长期记忆、后端存储等能力会放到后续章节继续讲。
5、多智能体设计边界
5.1 子智能体定义
很多同学第一次看到“子智能体”,容易把它想成“多写几个角色 Prompt”。比如:
你是搜索专家。
你是分析专家。
你是写作专家。
这只说对了一小部分。真正的子智能体,不只是一个角色名字,而是一个可以被主智能体调用的小助手。
可以先用一个生活例子理解:
如果你要装修房子,你不会一个人同时做设计、水电、木工、验收。更合理的方式是:
- 你负责确定总目标和预算;
- 设计师负责出设计方案;
- 水电师傅负责水电线路;
- 木工负责柜子和吊顶;
- 最后你把各部分结果汇总验收。
在 DeepAgents 里也是类似的:
- 主智能体负责理解用户目标、拆任务、决定交给谁做、最后整合结果;
- 子智能体负责完成某一类更具体的任务,比如搜索、分析、写作、审核。
所以子智能体可以简单理解为:主智能体请来的专业助手。
它的执行链路大致是这样:
用户目标
-> 主智能体判断任务太复杂,需要分工
-> 主智能体通过 task 工具把一部分任务交给子智能体
-> 子智能体独立完成这部分任务
-> 子智能体把结果返回给主智能体
-> 主智能体整合所有结果,回复用户
这里最关键的不是“Agent 数量变多了”,而是分工变清楚了。
如果没有清楚的分工,多智能体就容易变成多个模型互相聊天,调用次数变多了,结果却不一定更好。
5.2 主智能体和子智能体的关系
DeepAgents 更适合用“主从关系”来理解:
主智能体:项目负责人
├─ 搜索子智能体:负责查资料
├─ 分析子智能体:负责提炼观点
├─ 写作子智能体:负责组织报告
└─ 审核子智能体:负责检查遗漏和错误
主智能体不一定亲自做所有细节,它更像一个项目负责人,重点是:
- 理解用户目标;
- 判断任务该拆成哪些部分;
- 决定哪些部分自己做,哪些部分交给子智能体;
- 接收子智能体返回的结果;
- 把结果整理成最终答案。
子智能体则更像具体岗位的人,只关注自己负责的那一块。
例如在「深度研搜」项目里,一个用户问题可能是:请调研最近半年人形机器人行业的发展情况,并写一份带来源的分析报告。
这个任务并不是一句话就能回答好的。它可能需要:
- 先查新闻和政策;
- 再查企业和产品进展;
- 再整理趋势和风险;
- 最后写成报告。
这时就适合让主智能体统筹,让不同子智能体分别处理局部任务。
5.3 子智能体核心价值
子智能体不是为了让系统“看起来高级”,它主要解决三个实际问题。
第一,上下文隔离:避免主智能体被大量中间信息淹没。
深度搜索任务里,真正占空间的往往不是最终答案,而是中间过程:
- 搜索工具返回的很多网页内容;
- 文件读取返回的大段资料;
- 数据库查询返回的表格结果;
- 多轮补充搜索和反复对比产生的过程记录。
如果这些内容全部塞给主智能体,主智能体很容易被细节带偏,忘记最开始的目标。
子智能体的好处是:细节工作在子智能体那里完成,主智能体只拿回整理后的结论。
第二,能力专业化:让不同任务交给更合适的助手。
不同任务需要不同能力。比如深度研搜项目可以这样分工:
| 子智能体 | 负责什么 | 返回什么 |
|---|---|---|
| 搜索助手 | 查网页、新闻、报告、论文 | 来源、摘要、关键信息 |
| 分析助手 | 从资料中提炼观点 | 结论、依据、风险点 |
| 写作助手 | 把内容组织成报告 | 结构清楚、语言顺畅的正文 |
| 审核助手 | 检查遗漏、冲突和不确定性 | 修改建议、风险提醒 |
这样每个助手的任务更清楚,提示词也不用写得又长又杂。
第三,任务并行:多个方向可以同时推进。
如果一个调研任务可以拆成“政策、市场、技术”三个方向,而且它们互不依赖,主智能体就可以分别交给三个子智能体去做。
这样整体耗时可能会更短。但要注意,并行不是免费的。子智能体越多,模型调用次数、费用和调试难度也会增加。
5.4 子智能体使用边界
多智能体不是默认选项。判断是否要用它,可以先问一句:
这个任务真的复杂到需要分工吗?
先看适合使用的情况。一般来说,满足下面任意一种情况,才值得考虑多智能体:
| 判断标准 | 说明 | 示例 |
|---|---|---|
| 问题极度开放 | 任务没有固定路线,需要探索、试错和动态调整 | 行业研究、战略分析、开放式调研 |
| 存在领域冲突 | 任务涉及多个专业领域,放在一个上下文里容易互相干扰 | 法律 + 医疗、金融 + 工程、代码 + 产品 |
| 需要多方向并行 | 任务天然可以拆成多个独立方向并行推进 | 多源搜索、多方案设计、多文件分析 |
反过来,如果任务很简单,就不需要多智能体。
比如:
- “北京今天多少度?”直接调用天气工具就够了;
- “把这句话翻译成英文”一个普通 Agent 就够了;
- “总结这篇短文章”通常也不需要拆成多个子智能体。
但如果任务是:
从新闻、报告、论文、企业公告中调研某个行业,
并生成一份带引用来源、趋势判断和风险分析的深度报告。
这类任务就更适合多智能体,因为它需要多源资料、多步骤处理和最后汇总。
再看不适合使用的情况。子智能体不是越多越好,下面几种场景反而不建议拆:
| 不适合场景 | 为什么不适合 | 更合适的做法 |
|---|---|---|
| 一步就能完成的小任务 | 委派本身也要花时间和费用 | 直接回答或直接调用工具 |
| 必须连续阅读的短任务 | 拆开后反而容易丢上下文 | 让同一个智能体一次完成 |
| 子任务说不清楚 | 子智能体不知道该返回什么 | 先写清任务说明和输出格式 |
| 只是为了“看起来高级” | 会增加成本、日志和失败点 | 先用单 Agent 跑通 |
| 工具权限不好控制 | 子智能体可能调用不该调用的工具 | 明确工具白名单和审批规则 |
一个很实用的经验是:
先用单 Agent 跑通流程,再观察哪里反复变长、变乱、变专业,最后再把那一块拆成子智能体。
不要一开始就设计很多子智能体。初学时先掌握主线,比堆复杂架构更重要。
5.5 DeepAgents 中的委派机制
在 DeepAgents 中,主智能体想调用子智能体时,通常会使用一个特殊工具:task。可以把 task 理解成“派活”的动作。
本章只需要先了解:看到 task,通常就说明主智能体正在把一部分任务委派给子智能体。
至于 task 的参数长什么样、如何在 stream() 输出中识别它、普通工具调用和子智能体调用有什么区别,会放到第 2 章和第 3 章结合代码讲。子智能体的 name、description、system_prompt、tools 等配置字段,也会在第 3 章正式展开。
5.6 多智能体的缺陷
多智能体能解决复杂问题,但也会带来代价。
第一,调用次数和费用会增加。
每个子智能体都可能单独调用模型。子智能体越多,消耗的 Token 和费用通常也越多。所以不要因为“多智能体听起来更高级”就到处拆。简单任务直接让主智能体完成,反而更快、更便宜。
第二,排查问题会更麻烦。
单智能体出错时,通常只需要看一条执行链路。
多智能体出错时,需要追踪:
- 主智能体为什么选择这个子智能体;
- 子智能体收到了什么任务;
- 子智能体调用了哪些工具;
- 子智能体返回了什么;
- 主智能体如何整合这些结果。
所以后续写代码时,我们会特别关注 stream() 输出和日志。它们不是装饰,而是排查问题的重要线索。
5.7 两类多智能体架构
多智能体常见有两种组织方式。这部分先了解,不需要背概念。
第一种:层级模式,也叫指挥官模式。
层级模式(Orchestrator-Workers)的核心逻辑是:一个主智能体负责调度,多个子智能体负责执行。
可以把它理解成一个项目负责人带着多个专业助手工作。主智能体先理解用户目标,再拆解任务、选择合适的子智能体、收集子智能体结果,最后统一整理成最终输出。子智能体不需要关心全局目标,只需要把自己负责的局部任务做好。

它的流程通常是:
- 用户输入一个复杂任务;
- 主智能体理解目标,并拆成多个子任务;
- 主智能体把子任务分给不同的专业子智能体;
- 子智能体在自己的上下文里完成局部任务;
- 子智能体把结果返回给主智能体;
- 主智能体汇总、判断、整理成最终结果。
这种模式的优点是路线清楚、责任明确,比较适合工程项目落地。出问题时,我们也更容易追踪:是主智能体拆错了任务,还是子智能体执行错了任务,还是最后汇总时出了问题。
它的缺点也很明显:主智能体非常关键。如果主智能体一开始理解错了目标,或者分配错了子任务,后面的执行就会跟着偏。另外,所有任务都要经过主智能体调度,任务特别复杂时,主智能体也可能成为瓶颈。
DeepAgents 更偏向这种模式。它强调的是:主智能体负责规划和调度,子智能体负责专业执行。
第二种:协作模式,也叫网状模式。
协作模式(Collaborative Network)的核心逻辑是:多个智能体围绕同一个问题共享信息、互相补充、共同推进。
它更像多位专家开会。每个 Agent 都可以根据自己的角色和上下文发表意见,也可以根据其他 Agent 的输出继续补充、质疑或修正。这里不一定有一个绝对的“领导”,系统更依赖 Agent 之间的互动来让结果逐步收敛。

这种方式的优点是灵活,适合开放式探索、方案讨论、观点碰撞。比如多个专家一起评估一个商业方案、讨论一个技术路线,协作模式可能产生更多角度。
它的缺点是更容易失控。多个 Agent 可能不断补充、反驳、再补充,最后讨论很久却没有明确结论。调试时也更麻烦,因为你很难快速判断:最终答案到底是哪个 Agent 的判断影响最大。
本课程第一阶段先学习 DeepAgents 的层级模式:主智能体负责调度,子智能体负责执行。
5.8 和其他多智能体框架的区别
有了上面的两种架构,再看不同多智能体框架,就不只是记名字了,而是看它们更偏向哪种“组织方式”。
DeepAgents 更像层级/指挥官模式:主智能体先规划,再把任务分派给子智能体,最后统一汇总结果。
AutoGen 更像去中心化/网状协作模式:多个 Agent 像在一个群聊里讨论问题,根据上下文自由接话、互相补充。
CrewAI 更像角色和任务流程驱动的团队协作:开发者通常会先定义角色、任务和流程,再让不同 Agent 按设定完成各自职责。它可以做层级式流程,也可以做更灵活的角色协作。
| 框架 | 更像什么 | 主要组织方式 | Agent 之间怎么协作 | 更适合的任务 | 主要风险 |
|---|---|---|---|---|---|
| DeepAgents | 项目负责人带多个专业助手 | 层级调度 | 主智能体通过 task 等机制分派任务,子智能体完成后返回结果 | 深度研究、长任务、报告生成、多步骤资料整理 | 主智能体判断质量很关键,拆错任务会影响后续结果 |
| AutoGen | 多个专家在群里讨论 | 网状协作 | 多个 Agent 围绕上下文自由对话、互相补充、互相修正 | 头脑风暴、方案讨论、开放式探索、复杂问题求解 | 容易发散,调试时难追踪关键判断来自哪里 |
| CrewAI | 预设好的团队按任务流程协作 | 角色 + 任务流程 | 先定义角色、目标和任务,再让 Agent 按流程协作 | 标准业务流程、多角色执行、较清晰的团队分工 | 效果依赖角色和任务设计,流程设计不好时容易僵硬 |
简单说,三者最大的区别不是“谁更高级”,而是任务组织方式不同:
- 如果你希望有一个主智能体负责全局调度,让其他子智能体完成局部任务,DeepAgents 更合适;
- 如果你希望多个智能体自由讨论、互相启发,AutoGen 更合适;
- 如果你希望提前设计好团队角色和任务流程,CrewAI 更合适。
本课程重点学习 DeepAgents。原因很简单:它的主线更清楚,适合先建立工程化思维:
- 谁负责调度;
- 谁负责执行;
- 谁保存中间信息;
- 谁输出最终结果。
这也和「深度研搜」项目非常匹配。用户提出一个开放问题后,系统需要先规划,再搜索,再分析,再补充,再生成报告。这样的任务天然适合层级模式:主智能体把握全局,子智能体分别处理搜索、阅读、分析、写作等局部工作。
5.9 子智能体设计小清单
后续真正配置子智能体时,可以用下面这张清单检查:
| 检查项 | 问自己一句话 |
|---|---|
| 职责是否单一 | 它是不是只负责一类清晰任务? |
| 描述是否清楚 | 主智能体看完 description 后知道什么时候叫它吗? |
| 工具是否够少 | 它是不是只拿到了必须使用的工具? |
| 输出是否好用 | 它返回的是结论,还是一堆未经整理的过程信息? |
| 成本是否值得 | 这个任务真的值得多调用一次智能体吗? |
| 日志是否可查 | 出错时能不能看出它接了什么任务、做了什么? |
如果一个子智能体说不清楚:什么时候调用;输入应该是什么;输出应该是什么;不能做什么。那它就还不是一个好子智能体,只是一个听起来很厉害的名字。
本章小结:
这一章我们先从项目背景出发,理解了为什么 DeepAgents 会出现在普通 Agent 之后。普通大模型主要解决“回答问题”,工具型 Agent 进一步解决“调用工具”,而 DeepAgents 更关注“复杂任务如何规划、分工、执行和持续推进”。
接着,我们区分了 LangChain、LangGraph 和 DeepAgents 的定位:
LangChain更偏基础抽象;LangGraph更偏确定性工作流编排;DeepAgents更偏自主规划和多智能体组织。
然后,我们梳理了 DeepAgents 的四个核心能力:任务规划、上下文管理、子智能体和长期记忆。它们共同服务于一个目标:让智能体能处理更长、更复杂、更开放的任务。
最后,我们重点补齐了子智能体的认知基础:子智能体不是多写几个角色 Prompt,而是通过 task 工具被主智能体委派的独立执行单元。它的核心价值在于上下文隔离、能力专业化和可控并行。同时我们建立了一个重要工程判断:多智能体不是越多越好。只有当任务足够开放、存在领域冲突,或者天然需要多方向并行时,才值得承担多智能体带来的成本与调试复杂度。
本章打的是认知基础。下一章会正式进入代码层面,创建第一个 DeepAgent,并学习如何解析 invoke() 和 stream() 的执行结果;第 3 章再专门展开子智能体的配置、调度和异步执行。
大模型微调概述与整体流程
28 - 大模型微调概述与整体流程
本章课程目标:
- 能解释微调与推理的区别,说明监督微调用什么样的样本训练模型。
- 能根据实际问题,选择先调整提示词、补充资料、调用工具,还是考虑微调。
- 能分清预训练、监督微调与偏好对齐的基本作用,并阅读模型介绍选择合适的起点。
- 能说明 SFT、LoRA 与 QLoRA 的关系,理解本课程为什么可以“用 LoRA 做 SFT”。
- 能写清任务要求、保存基线,并说出数据准备、训练、评估和导出各阶段的主要产物。
学习建议: 先拿图书馆短文分清输入、期望输出和实际问题,再沿着“判断是否需要微调 → 选择模型 → 准备数据 → 训练 → 检查效果”建立整体认识。第一遍先讲清每一步为什么要做、会得到什么;公式和训练参数留到第 30 章。读完后按第 7 节整理任务约定与基线记录,作为后续几章共同使用的起点。
官方文档与资源: 详见工具导航与参考资料索引 - 微调与模型对齐。
1、微调项目从一个具体问题开始
1.1 任务说明
假设我们正在开发一个文章管理系统。每篇文章除了标题和正文,还需要几个关键词,帮助读者快速了解主题,也方便程序检索和整理内容。
文章少时,可以人工填写;文章多了,就希望模型读完正文后自动提取。这就是关键词抽取:从一段文本中找出能够概括主题的词或短语。
先看一个容易理解的例子:
文章:
市图书馆周末开设儿童阅读课,读者可通过公众号预约。
参考关键词:
市图书馆;儿童阅读课;公众号预约
为什么选这几个关键词?
- 市图书馆说明谁开设课程。
- 儿童阅读课说明开展了什么活动。
- 公众号预约保留了怎样参与这一信息。
相较之下,“开设”“通过”虽然出现在原文中,却很难单独概括文章主题。关键词抽取不是随便挑几个出现过的词。
这里的关键词是为了讲解而给出的一组参考答案,并不表示这段话只有一种合理选法。实际准备训练数据时,需要统一选词的范围和写法。
输出格式要求:
假设程序收到下面的结果:
市图书馆;儿童阅读课;公众号预约
它就可以按照英文分号 ; 拆成三个关键词,再保存或展示。
但如果收到的是:
这篇文章介绍了市图书馆的阅读课,关键词包括:
1. 市图书馆
2. 儿童阅读课
3. 公众号预约
人仍然看得懂,程序却不能直接按同样的规则处理,还要额外清除说明文字和序号。
因此,本课程的要求是:输入中文文本,输出主题关键词;关键词之间使用英文分号,不添加解释、序号和“关键词:”前缀。 英文分号是本项目约定的输出格式。
第 28~33 章会围绕这项任务学习数据准备、训练和验证。图书馆短文是教学示例;实际练习使用课程附带的关键词数据,第 29 章会打开真实记录逐项查看。
1.2 先保存原始回答:认识基线
Baseline(基线)是用来比较的起点:把文本和输出要求交给尚未做本次任务微调的模型,原样保存它的回答。后续用这份记录判断微调带来了哪些改善。
沿用图书馆教学示例,假设模型返回以下回答,可以分别记下哪些问题?
| 假设模型这样回答 | 先记下什么问题 |
|---|---|
关键词:市图书馆;儿童阅读课;公众号预约 | 选词基本符合要求,但多了前缀 |
市图书馆,儿童阅读课,公众号预约 | 内容可以,但没有使用规定的分隔符 |
市图书馆;读者 | 格式正确,却没有概括“儿童阅读课”这一主要内容 |
市图书馆;儿童阅读课;公众号预约 | 这条样本暂未发现明显问题,还要看其他文本 |
检查结果要分成两件事:格式是否符合约定,内容是否抓住主题。
比较时,先用同一段文本、同一份任务说明。如果训练前只说“找出关键词”,训练后才补上“使用英文分号”,即使格式变好了,也不能直接把改进归功于微调。
1.3 错误原因的初步判断
假设模型回答时多写了“关键词:”。这可能是它不擅长遵守格式,也可能只是我们没有把要求说清楚。
如果补一句“不要添加前缀”就能解决问题,先采用修改后的提示词。要求已经明确、换了多段文本后仍出现同类错误,再继续排查资料、模型和训练数据。
2、微调前的问题诊断
2.1 明确提示词要求
在前面的提示词工程基础中,我们学过:任务说明越含糊,模型越容易按自己的方式回答。这里直接把这个方法用到关键词任务中。
只写“提取关键词”,没有说明输出格式。可以先改成:
请从下文中抽取能够概括主题的关键词。
只输出关键词,关键词之间使用英文分号分隔。
不要输出“关键词:”、序号和解释。
待抽取文本:
市图书馆周末开设儿童阅读课,读者可通过公众号预约。
再观察模型的回答。如果还有格式问题,可以在 Prompt 中补充一组“文本 → 期望答案”的示例,让模型看见我们希望的写法。这就是前面介绍过的 Few-shot Prompt,即用少量示例说明任务。
补充示例后,再用没有展示过答案的短文、长文和专业文本检查。模型能照着图书馆示例输出,并不能说明它能处理新文章。
测试过程中,每次改过的 Prompt 都单独保存。这样才能知道哪次修改有帮助。
到第 32 章第 2.4 节,会把这一步落实为“原始指令 → 明确要求 → 加入示例”的验证练习。先确定提示词,再让原始模型与微调后的模型使用同一份提示词比较;本章先理解这个顺序。
2.2 不同问题的处理方式
如果 Prompt 已经写清楚,结果仍不理想,就要看模型到底卡在哪里。
| 观察到的问题 | 可以怎样理解 | 下一步先尝试什么 |
|---|---|---|
| 不知道需要英文分号 | 没有收到明确的格式要求 | 修改 Prompt,必要时加入示例 |
| 不认识企业刚发布的产品名称或缩写 | 需要原文之外的补充资料 | 提供产品资料,或通过 RAG 检索 |
| 原文信息完整,但小模型总是抓不住主题 | 当前模型可能不适合这类文本 | 用相同文本比较其他候选模型 |
| 要求清楚、示例一致,多篇文本仍反复出现同类错误 | 模型完成这项任务还不够稳定 | 准备有针对性的数据,评估微调 |
| 提取关键词后还要保存到数据库、发送通知 | 任务包含模型回答之外的操作 | 用程序、Workflow 或 Agent 调用工具 |

这些方法可以搭配使用。例如,先检索产品术语表,再让模型抽取关键词,最后由程序保存结果。不是选了微调,就不再需要 Prompt 或工具。
对于图书馆短文,主要信息已经在原文中,一般不必为了抽取关键词再建一个知识库。对于“查询今天的阅读课还有几个预约名额”,则需要查询实时数据,不能指望微调把每天的名额变化都记住。
方案之间的完整比较见第 1-3 章。
2.3 微调的适用场景
提示词和候选模型已经检查过,同类错误仍反复出现时,可以评估微调。前提是有明确的改进目标,也有正确、写法一致的样本,并能留出未参与训练的文本检查效果。
相反,如果今天要求输出分号,明天改成一段解释,训练数据里又混着相互矛盾的答案,应该先统一任务和数据。模型很难从这样的示例中学到一致的做法。
还有一种常见需求:较大的模型效果不错,但运行成本较高,希望较小的模型也能完成这项相对固定的任务。可以尝试微调,再结合实际效果和成本决定是否采用。
3、微调的基本概念
3.1 微调与推理的区别
前面的章节中,我们主要使用已经训练好的模型:输入提示词,获得回答。这种使用模型处理输入、生成结果的过程叫作推理(Inference)。
微调则要多做一步——用任务样本继续训练,更新模型中可训练的参数。 参数,也叫权重,是模型内部参与计算的大量数值;调整这些数值,会影响模型怎样理解输入、生成回答。
可以把微调理解为给已经会读写的助手做专项练习。它不是从零开始学习中文,而是在已有能力上,学习更符合要求的关键词选择和输出方式。
回到图书馆案例,两种做法的区别是:
| 做法 | 我们提供什么 | 有没有更新权重 |
|---|---|---|
| 修改 Prompt,让模型回答 | 本次任务说明,以及可参考的示例 | 没有,模型根据当前上下文回答 |
| 用样本进行微调 | 多条文本和对应的标准关键词 | 有,训练程序更新可训练部分 |
因此,微调属于训练(Training)。在聊天框中告诉模型“以后只用英文分号”,即使它接下来照做了,也不等于完成了一次微调。
微调之后,使用模型时仍需提供任务说明和待处理的文章。
3.2 监督微调(SFT)
本课程采用监督微调(Supervised Fine-Tuning,SFT)。“监督”指的是样本里提供了希望模型学习的参考答案。
第 1.1 节的图书馆短文和参考关键词,就可以用来理解一条样本的两个部分:
- 输入:待处理的文章,以及“找出上文中的关键词”这类任务指令。
- 标准输出:符合选词要求、使用英文分号的参考关键词。
训练程序利用这样的样本调整可训练参数,让模型学习文章与关键词之间的对应关系,以及我们约定的输出方式。它能不能把这种做法用到新文章上,还需要检查。
3.3 SFT 与 LoRA 是什么关系
后面打开训练工具时,我们会同时选择 SFT 和 LoRA。已经选了监督微调,为什么还要选一种微调方式?因为它们回答的是两个问题:
| 要决定什么 | 本课程的选择 | 放到关键词任务中理解 |
|---|---|---|
| 用什么样的反馈教模型 | SFT:提供输入和可靠的参考答案 | 给文章,再给一份符合要求的关键词示范 |
| 允许更新哪些参数 | LoRA:冻结原权重,训练新增的小部分参数 | 用少量可训练参数学习这项任务的调整 |
因此,“用 LoRA 做 SFT”是一套可以组合的方案。同样的监督微调任务,也可以采用全参数微调,更新原模型中的大量参数。两者最后都要通过实际回答检查效果。
第 30 章还会介绍 QLoRA:在量化基础权重的同时训练 LoRA 分支,以降低部分显存开销。“量化”先理解为用更少的位数近似保存数值。它改变了权重的保存与计算安排,并没有把“文章 + 参考关键词”变成另一种任务。
SFT 说明怎么教,LoRA 说明主要改哪里,QLoRA 进一步考虑怎样节省显存。 第 30 章再展开具体计算和参数设置。本课程首次跟做使用已经验证的普通 LoRA 路线。
4、模型类型与模型选择
理解了“在已有模型上继续训练”,接下来要确定这个“已有模型”是哪一个。
本课程使用 Qwen/Qwen3-0.6B。但在模型库中,你还会看到名称非常相近的 Qwen/Qwen3-0.6B-Base。两者有什么区别?下载时应该选谁?
4.1 基础模型与指令模型
先回顾第 1-1 章讲过的模型训练过程:
| 训练阶段 | 模型主要学习什么 | 可以怎样理解 |
|---|---|---|
| 预训练 | 从大量语料中学习语言规律和知识关联 | 先具备阅读、续写等基础能力 |
| 监督微调 | 根据指令学习标准回答 | 不只是继续写文字,还要完成“总结、分类、抽取”等任务 |
| 偏好对齐 | 学习在候选回答中更倾向于符合要求的回答 | 不只看回答有没有错,也比较怎样回答更合适 |
Base Model(基础模型)通常指主要完成预训练、尚未经过面向助手的指令训练的模型。它可以继续写文本,但不一定稳定按照聊天指令完成任务。
Instruct Model(指令模型)经过了指令训练,通常更适合直接处理“请总结”“提取关键词”“只输出指定格式”等要求。预训练之后的指令训练、偏好优化等过程,也常统称为后训练。
它们的大致关系如下。这是一条帮助理解的常见路线,不代表每个模型的训练步骤和顺序都完全相同。
flowchart LR
A["大规模语料"] --> B["预训练"]
B --> C["Base Model<br/>基础模型"]
C --> D["监督微调 SFT<br/>学习执行指令"]
D --> E["Instruct Model<br/>指令模型"]
E -. "可继续进行" .-> F["偏好对齐<br/>学习更合适的回答方式"]
classDef data fill:#f8fafc,stroke:#94a3b8,color:#334155;
classDef train fill:#eff6ff,stroke:#2563eb,color:#1e3a8a;
classDef model fill:#ecfdf5,stroke:#0f766e,color:#115e59;
class A data;
class B,D,F train;
class C,E model;
4.1.1 模型系谱
打开 ModelScope 的 Qwen3-0.6B 模型页,在页面右侧找到“模型系谱”。
“系谱”就是来源关系:这个模型是在谁的基础上继续训练或处理得到的。

截图中,基模型一栏是 Qwen/Qwen3-0.6B-Base,下面通过“微调”关联到当前模型。对应关系可以读成:
Qwen/Qwen3-0.6B-Base
↓ 官方继续进行后训练
Qwen/Qwen3-0.6B
↓ 本课程继续做关键词任务的监督微调
面向关键词抽取任务的模型
最后一步是本课程接下来要完成的练习。官方模型的后训练方式以模型说明为准。
页面中的几个名字可以这样区分:
| 名称 | 含义 | 什么时候会用到 |
|---|---|---|
千问3-0.6B | 页面展示给人看的中文名称 | 浏览、辨认模型 |
Qwen/Qwen3-0.6B | 当前模型的完整仓库 ID | 下载模型、填写工具配置 |
Qwen/Qwen3-0.6B-Base | 对应 Base 版本的仓库 ID | 查看模型来源,或明确需要 Base 版本时使用 |
本课程选择的是 Qwen/Qwen3-0.6B,不是 -Base 版本。 它已经经过后训练,能够执行指令,并支持思考与非思考模式。这些信息可以在官方模型说明中核对。
指令模型的名称不一定包含 Instruct。下载时使用页面给出的完整仓库 ID,不要自行加上 -Instruct 后缀。
4.2 偏好对齐
偏好对齐可以通过比较候选回答,让模型更倾向于符合要求的回答方式。它也是后训练的一部分;本课程在已经过后训练的 Qwen3-0.6B 上继续做关键词 SFT。
假设我们在做一个办事问答助手。用户材料没有交齐,两个回答都指出了这个问题:
回答 A:材料不完整,无法办理。
回答 B:还缺少申请表,请补充后再提交。
如果已知缺少的确实是申请表,B 比 A 更具体,也更能帮助用户继续操作。我们可以把这种“更希望助手怎样回答”的选择记录下来,用于偏好训练。
4.3 ModelScope 模型页面
仍然留在刚才的 ModelScope 页面,接着看两个位置。
4.3.1 模型介绍
模型介绍页里的说明,也常叫模型卡。 可以把它当作模型的使用说明书。
先找到发布者 Qwen 和仓库 ID,确认没有进入名称相近的第三方模型页面;然后阅读模型概述,了解它的规模、用途和使用方法。
以本课程模型为例,官方模型说明列出的参数规模是 0.6B。B 表示 billion,即十亿,因此 0.6B 大致表示 6 亿个参数。这里说的是模型内部的数值数量,不是文件有多少 GB;规模可以作为选型参考,但模型是否好用还要通过任务测试。
选读:截图中的 751.63M 为什么与 0.6B 不同
M 表示百万,751.63M 约为 7.52 亿。该版本权重文件的清单同时保存了输入嵌入和输出层权重,各项数量合计为 751,632,384。但 Qwen3-0.6B 加载后在这两个位置共用一组权重。共享部分只计算一次,独立参数量为 596,049,920,约 5.96 亿,对应官方标注的 0.6B。
这一共享设置可以在官方配置文件中的 "tie_word_embeddings": true 核对。
4.3.2 模型文件
点击“模型文件”,可以看到模型不是一个孤立的文件,而是权重与多种配置文件组成的目录。

先认识几类后面会用到的文件:
| 文件或文件类别 | 可以怎样理解 |
|---|---|
model.safetensors | 保存模型已经学到的权重数值 |
config.json | 描述模型结构需要的配置,例如层数等信息 |
generation_config.json | 保存一些默认生成设置 |
| 分词器相关文件 | 帮助程序把文字转换成模型能够处理的编号,并把输出编号还原成文字 |
截图中的 model.safetensors 约为 1.50 GB。这是文件占用的存储空间,与“0.6B 个参数”不是同一个单位;文件大小也不能直接当作训练需要的显存。
加载模型时,程序先根据模型实现和配置搭起计算结构,再读取权重和分词器等文件。因此,后面下载模型时要保留完整的必要文件,而不是只拿一个权重文件就结束。
4.4 模型试用与选择
先选 5~10 条有代表性的文本,覆盖短文、长文和专业术语,使用相同的任务说明比较候选模型。按第 1.2 节的内容和格式要求记录问题。这种小规模试用适合筛选候选模型,正式评估仍需使用独立数据。
候选模型有在线试用或 API 时,可以先观察回答,再决定是否下载。本课程固定使用较小的 Qwen/Qwen3-0.6B,便于完成数据、训练和验证的整套练习。
5、微调数据准备
5.1 训练样本的基本要求
确定使用哪个模型后,接下来就要准备第 3.2 节所说的“输入与标准答案”。
对关键词任务来说,文章要接近实际业务,参考关键词要能概括主题,输出也要符合分号格式。不能一边要求只输出关键词,一边又在标准答案里放进长段解释。答案本身有错,模型也可能学到错误示范。
课程提供了包含 2,000 条记录的关键词样本文件 案例与源码-4-微调/keywords_data_sharegpt_small.jsonl。第 29 章将检查、清洗这些样本,再用处理后的数据训练。
5.2 数据划分的作用
模型能答好练过的文章,并不代表换一篇也能做好。因此,要在训练之前留出一部分数据,专门检查新文章上的表现。
后面会按用途把数据分成三份:
- 训练集:交给训练程序,让模型学习。
- 验证集:训练和调整方案时,用来检查效果。
- 测试集:方案确定后,用来做最后的独立比较。
比如要比较“训练一轮还是三轮”,用验证集帮助选择;测试集留到方案确定后再使用。
6、微调的整体流程
6.1 主要步骤
把前面的内容连起来,一次微调项目就可以按下面的顺序进行:
flowchart TD
A["选择模型<br/>试用提示词,保存原始回答"] --> B["准备数据<br/>检查、清洗并固定划分"]
B --> C["执行训练<br/>让模型学习训练样本"]
C --> D["用验证集检查效果"]
D --> E{"是否选定方案?"}
E -- "继续调整" --> F["分析错误<br/>调整训练数据或设置"]
F --> C
E -- "是" --> G["使用独立测试集<br/>比较微调前后的回答"]
G --> H["根据结果决定是否使用<br/>满足要求后再导出、部署"]
classDef prep fill:#eff6ff,stroke:#2563eb,color:#1e3a8a;
classDef decision fill:#fff7ed,stroke:#f97316,color:#9a3412;
classDef result fill:#ecfdf5,stroke:#0f766e,color:#115e59;
class A,B,C,D,F,G prep;
class E decision;
class H result;
检查时发现答案中总有多余解释,就回看任务说明和训练样本:是规则没写清楚,还是数据本身也带着解释?找到原因后调整,再次检查。实际结果符合使用要求后,才接进程序。
6.2 微调效果的检查
回到第 1.2 节记录的错误:微调后是否还会多写前缀、用错分隔符、漏掉文章主题?用相同输入和任务说明比较,才知道这些问题有没有改善。
例如,不再输出“关键词:”,说明格式有所改善;但如果同时漏掉更多主题关键词,就不能只展示格式变好的那部分结果。
第 31 章会实际完成训练;第 32 章再把模型加载起来,逐条查看回答、批量评分,并学习如何导出使用。
7、任务约定与基线记录
7.1 任务约定
整理数据、填写训练配置和比较回答时,统一使用下面的任务约定。
| 项目 | 本课程约定 |
|---|---|
| 任务 | 中文文本关键词抽取 |
| 输入 | 一段中文文本和关键词抽取指令 |
| 输出 | 能够概括主题的关键词,用英文分号 ; 分隔 |
| 不需要的内容 | 解释、序号、Markdown 列表和“关键词:”前缀 |
| 模型 | Qwen/Qwen3-0.6B |
| 训练方式 | 用输入与标准答案做 SFT,采用 LoRA |
| 数据 | 清洗后固定划分的训练集、验证集和测试集 |
| 格式检查 | 是否遵守输出约定 |
| 内容检查 | 选出的词是否准确,主要主题是否遗漏 |
| 对比对象 | 同一个模型在本次微调前后的表现 |
表中的 LoRA 是本课程采用的微调方法,通常比更新模型全部权重节省训练资源,原理见第 30 章。
7.2 基线记录示例
一次基线记录应让别人知道:模型读到了什么、实际回答了什么,以及在什么条件下运行。可以按下面的表格保存:
| 记录项 | 保存什么 |
|---|---|
| 样本来源与完整输入 | 文件名、样本位置、文章全文和任务说明 |
| 参考答案 | 人工核对后的关键词,单独保存供检查使用 |
| 模型与运行设置 | 完整模型 ID、版本,以及工具使用的生成设置 |
| 原始回答 | 原样保存模型返回的全部文字,包括不完整或不符合要求的回答 |
| 问题记录 | 分开记录格式错误与内容遗漏,供微调后对照 |
用第 1.2 节的假设回答练习:关键词:市图书馆;儿童阅读课;公众号预约 应完整放入“原始回答”,并在“问题记录”中写“选词符合参考答案,多了前缀”。清除前缀再保存,就丢掉了需要比较的问题。
正式比较时,原始模型应与微调所用的起点版本一致,并保持相同的输入和生成设置。不明配置的在线试用回答只能用于初步观察,不能直接当作本地模型的基线。示例回答与运行设置见第 32 章的原始模型对照。
章节思考题:
- 微调与日常聊天推理有什么区别?在聊天框中提供几个正确示例,和把这些示例用于监督微调,分别改变了什么?
参考思路: 推理用当前参数处理输入,聊天示例成为本次输入的上下文;监督微调则由训练程序根据样本更新可训练参数。微调后仍需提供当前任务说明和待处理文章,模型不会自动知道下一次业务输入。
- 预训练、监督微调和偏好对齐分别帮助模型学习什么?选择 Base 模型或指令模型时,应看哪些信息?
参考思路: 预训练主要学习语言规律和知识关联,监督微调用参考答案教模型完成指令,偏好对齐让模型更倾向于符合要求的回答。Base 通常主要完成预训练,指令模型经过面向指令的训练。选择时查看模型说明、输入方式、任务表现与资源要求;模型的实际训练过程和仓库名称以介绍为准,不能只凭 Instruct 后缀判断。
- “用 LoRA 做 SFT”中,SFT 和 LoRA 各决定什么?换成 QLoRA 后,文章与参考关键词这类训练样本需要变成另一种任务吗?
参考思路: SFT 决定用输入与参考答案提供学习信号;LoRA 决定冻结基础权重,主要训练新增分支。QLoRA 进一步量化基础权重,以减少这部分存储开销,仍可用同样的文章与参考关键词做 SFT。训练目标与参数更新方式是可以组合的选择。
- 模型多写了“关键词:”、不认识企业刚发布的产品缩写、需要查询今天的活动余位,这三类问题分别优先怎样处理?什么情况下再考虑微调?
参考思路: 前缀问题先明确提示词并换几篇文章检查;缺少术语资料时补充原文或检索资料;实时余位通过查询工具取得。任务要求、资料和候选模型已经检查过,仍反复出现同类行为问题,并且有可靠示范与评估数据时,再评估微调的投入与收益。几种方法也可以组合使用。
- 为“文章 → 关键词”准备一条监督微调样本,至少要提供什么?为什么只有一篇原始文章还不够?
参考思路: 要提供任务要求、待处理文章,以及有原文依据、符合选词和分隔符约定的参考关键词。原始文章只提供了材料,没有说明本课希望模型生成怎样的答案。整理参考答案时既要检查格式,也要核对内容;实际使用新文章时,再检查模型是否学会了这种对应关系。
- 怎样建立一份能用于微调前后比较的基线?如果训练后同时换了提示词,分数变化该怎样解释?
参考思路: 保存模型版本、任务说明、代表性输入、参考答案、原始回答和生成设置,分别记录格式与内容问题。比较微调影响时,让原模型和加载 Adapter 的模型使用相同条件;同时换提示词会引入另一项变化,不能把全部收益归因于微调。具体对照在第 32 章完成。
- 从一个待改善的关键词任务开始,到得到可检查的训练结果,主要经过哪些步骤?每一步应留下什么?
参考思路: 先明确任务并保存基线,再选择模型、审核和划分数据,随后配置训练并保存 Adapter、日志和检查点,最后比较回答、记录错误并学习导出。数据准备留下训练、验证和测试集;验证集用于调整方案,测试集用于方案确定后的检查。任务约定、数据记录、训练配置和评估结果要能对应起来,便于复现和继续改进。
本章小结:
- 微调是在已有模型上继续训练,通过样本更新可训练参数,使模型适应任务。推理使用当前参数生成回答,在聊天框中增加示例不会自动完成参数更新。
- 提示词说明任务,外部资料补充知识,工具执行查询与操作。是否需要微调,应从实际错误、业务要求和已有方案的表现出发。
- 预训练、监督微调和偏好对齐分别侧重基础能力、执行指令和回答偏好。选择 Base 或后训练模型时,要结合模型说明、代表性输入和资源条件判断。
- SFT 用输入与参考答案提供学习信号,LoRA 主要训练新增分支,QLoRA 进一步量化基础权重;它们分别涉及训练目标、更新参数和权重表示方式。
- 本课程沿着任务约定、基线、数据准备、训练、评估和导出展开。各阶段的文件与记录要能对应,格式改善、内容改善和处理新文章的能力需要分别检查。
建议下一步: 按第 7 节完成任务约定与基线记录,试着说明“要改善什么、为什么考虑微调、准备怎样比较”。随后进入第 29 章,把可靠示范整理成可用于训练和检查的数据。
微调数据准备与对话模板
29 - 微调数据准备与对话模板
本章课程目标:
- 能分清 JSONL 的文件组织方式与 Alpaca、ShareGPT 的样本结构,找到任务、输入和参考答案。
- 能对照原文审核答案,处理格式、重复与标注分歧,并判断数据还缺少哪些场景。
- 能说明训练集、验证集和测试集的用途,完成清洗与固定划分,检查数据泄漏和处理报告。
- 能解释字段映射、对话模板和分词器各自的职责,分清训练与推理时提供的内容。
- 能整理后续训练所需的数据与配套记录,并说明自动检查和人工审核各完成了什么。
学习建议: 先按第 1~3 节用一条图书馆样本认清任务、文章和答案,审核内容并比较两种数据结构;再到第 5 节清洗、划分数据,最后理解第 6 节的输入转换。第 3.3 节工具调用与第 4 节文档问答为选读,按需要练习;第 6 节代码等第 31 章环境准备好后再运行。章末先独立作答,再逐题核对参考思路。
1、认识微调数据
第 28 章中,我们希望模型完成一件很具体的事:读一段中文文本,抽取能够概括主题的关键词,只使用英文分号 ; 分隔,不输出解释。
现在把这个要求变成训练样本。沿用第 28 章的图书馆教学示例,它不属于课程数据文件:
输入:市图书馆周末开设儿童阅读课,读者可通过公众号预约。
请提取关键词,只输出关键词,并使用英文分号分隔。
参考答案:市图书馆;儿童阅读课;公众号预约
输入告诉模型“读什么、做什么”,参考答案告诉它“希望怎样回答”。制作自己的数据时,这份答案可能来自人工标注,也可能由另一个模型起草后再审核;不能把未经检查的回复直接当成正确答案。
现在只找本机 案例与源码-4-微调/ 中的 keywords_data_sharegpt_small.jsonl,它是本章的起点。第 5 节再使用清洗脚本和处理结果,训练、预测与导出文件在后续章节用到时查找。
查阅:案例目录与各类文件的用途
先认识案例文件。 在课程仓库中打开 案例与源码-4-微调/,主要文件如下。先找到 small 样本、清洗脚本和处理结果目录,其他文件用到时再展开。
案例与源码-4-微调/
├── README.md # 数据与使用说明
├── keywords_data_sharegpt_small.jsonl # 本章使用的 2,000 条关键词样本
├── keywords_data_sharegpt.jsonl # 约 5 万条,供扩展练习使用
├── prepare_keywords_dataset.py # 清洗、去重并划分数据
├── evaluate_keywords_predictions.py # 第 32 章评分,清洗脚本也复用其格式检查
├── convert_keywords_predictions.py # 第 32 章转换预测文件
├── processed/ # 已处理的数据
│ └── keywords-clean/ # 课程使用的训练集、验证集和测试集
│ ├── keywords_train.jsonl # 训练集:1,600 条,用于学习和更新参数
│ ├── keywords_validation.jsonl # 验证集:200 条,训练中检查表现
│ ├── keywords_test.jsonl # 测试集:200 条,方案确定后检查最终效果
│ ├── cleaning_report.json # 清洗报告:检查了什么、修改了什么
│ ├── manifest.json # 数据说明单:条数、划分规则与文件校验值
│ └── dataset_info.json # 数据集登记:告诉训练工具怎样读取三份数据
├── document-qa-demo/ # 第 4 节的文档问答练习
├── configs/ # 后续训练、预测和导出配置
├── examples/ # 提示词、模板、标注审核与预处理示例
└── results/ # 后续章节用到的训练曲线和预测结果
约 5 万条的完整数据为本目录中的 keywords_data_sharegpt.jsonl,主线跟做使用 small 文件即可。它包含 small 样本,不能把两者分别当成互不重叠的训练集和测试集。
上面列的是案例目录的主要入口,不都是训练数据。 三份数据集是 keywords-clean/ 里的 keywords_train.jsonl、keywords_validation.jsonl 和 keywords_test.jsonl;Python 脚本负责处理数据,配置文件告诉工具怎样运行,报告和记录则保存处理、运行的结果。
本课程从 small 文件的 2,000 条样本出发,经过处理和划分,得到 1,600 条训练集、200 条验证集和 200 条测试集。这三份数据都保留了输入与参考答案,只是用途不同:训练集用于更新模型参数,验证集用于训练中检查表现但不更新参数,测试集留到方案确定后再使用,不参与训练或调参。
processed/keywords-clean/ 是课程附带的处理结果。第 5 节会带你运行脚本,另存到 keywords-clean-repro/,并说明怎样划分、为什么要分开。这里先认识文件和用途。
现在打开 keywords_data_sharegpt_small.jsonl。文件名里的 small 表示它是供练习使用的小份数据,共 2,000 条;不是一种新的数据格式。
这个文件是 JSONL:一行就是一条 JSON 记录。第 1 条样本介绍“高氟铍矿石”的冶炼处理,原始一行很长;下面按原字段和值自动换行,只是为了看清结构,没有改写数据内容。
文件中的样本保存了用户输入和助手参考答案,主要字段如下:
conversations:这条样本中的整段对话;role:这句话是谁说的;content:这句话的具体内容。
本例中,user 消息包含待抽取的文本和任务指令,assistant 消息保存参考关键词答案。
在图中找到三处:待处理的文章、“找出上文中的关键词”这句要求,以及最后用分号分隔的答案。它们与上面的图书馆例子一一对应。
图中展示的是文件中的源记录;标题“模型到底会看到什么”先帮助我们认识文章和参考答案,实际送入模型的内容还要经过第 6 节的模板处理。图内长行有溢出,完整文字见下方展开内容。
展开阅读第 1 条完整记录(原字段与内容保留)
{
"conversations": [
{
"content": "高氟铍矿石在熔炼过程中配入氢氧化铝来脱除其中的氟.结果表明,在配入5%Na2CO3、9.3%Al(OH)3、1400~1500℃熔炼20 min的情况下,BeO回收率达到96%以上,脱氟效果良好(铍玻璃F/BeO能控制在15%以内).为高氟铍矿石的工业应用探索出新的冶炼途径.\n找出上文中的关键词",
"role": "user"
},
{
"content": "高氟铍矿;配料;熔炼;回收率;脱氟率",
"role": "assistant"
}
]
}
这里只为阅读展开排版,保存为 JSONL 时仍是一条物理行,消息中的换行写作 \n。
数据准备工作:
从文章到训练文件,中间还有几件事:选取符合任务的文章、核对答案、修正格式、处理重复样本,再分出训练和检查时使用的数据。把这些工作连起来,就是微调中的数据工程,也就是本章要完成的数据准备。
2、数据来源与质量要求
2.1 数据来源与获取方式
微调数据的内容和答案形式应符合目标任务。
| 数据来源 | 适合做什么 | 常见问题 |
|---|---|---|
| 公共数据 | 入门练习,或作为与任务匹配的训练数据 | 可能与自己的任务无关 |
| 真实业务数据 | 让模型适应真实业务场景 | 原始格式常常混乱,答案也未必统一 |
| 合成数据 | 补齐稀缺场景、扩充边界样本 | 生成得快,但仍然需要检查答案 |
例如,要做企业内部制度问答,通用对话数据可以帮助模型学习问答形式,但“公司年假有多少天”仍需以本公司的制度为准。准备这类任务的数据时,可以结合企业制度、已有问答、客服工单和人工核对的标准答案。
如果真实样本不足,也可以围绕任务规则生成模拟样本,再抽样检查答案是否正确、是否符合业务要求。
2.1.1 ModelScope 数据集查找
公共数据可以从 ModelScope(魔搭社区)的数据集页面查找。前面介绍的“模型库”用来找模型,“数据集”栏目则用来找训练或评测所需的数据,包含文本、图像、音频等类型。
如果已经知道数据集名称,可以在页面上方直接搜索;还没有具体目标时,可以先按左侧的任务分类浏览。例如,做对话或问答任务时,选择“文本”下的“智能对话”或“问答”,再从列表中查看相关数据集。

例如,打开 Alimeeting4MUG 数据集,可以看到它面向中文会议场景,包含关键词抽取、摘要等任务。页面提供“数据集介绍”“数据预览”和“数据集文件”三个入口:

| 页面入口 | 重点看什么 |
|---|---|
| 数据集介绍 | 数据从哪里来、使用什么语言、面向什么任务、怎样标注,以及使用许可是否符合自己的用途。 |
| 数据预览 | 直接查看几条输入和答案,判断文本内容、长度和答案形式是否合适。不是每个数据集都支持在线预览。 |
| 数据集文件 | 查看实际文件及其格式、大小,确认是否已有训练集、验证集或测试集,并按页面说明下载。 |
如果数据集没有在线预览,可以先阅读介绍页中的结构样例和加载说明,或下载少量数据检查,不必一开始就下载整套。下载后仍需清洗数据,并整理成后文介绍的 Alpaca 或 ShareGPT 等格式,才能接入本课程的训练流程。
2.1.2 常用数据准备工具
当你开始处理自己的文档时,可以了解下面三个数据准备工具:
| 工具 | 能帮我们做什么 | 什么时候值得了解 |
|---|---|---|
| Easy Dataset | 通过界面导入文档、拆分内容、生成和编辑问答数据 | 想先把一份文档整理成可检查的问答样本 |
| DataFlow | 把数据提取、转换、清洗、筛选和生成等步骤组织成处理流程 | 文档较多,希望批量执行一套数据处理规则 |
| GraphGen | 利用知识图谱中的实体及其关系,辅助生成训练数据 | 数据需要体现多个知识点之间的联系,可作为进阶工具了解 |
第 4 节的独立扩展用 Easy Dataset 完成“导入产品说明 → 生成问答 → 审核 → 导出”。DataFlow 和 GraphGen 可供批量处理与进阶练习时选用。
2.2 关键词标注的质量要求
怎样判断一份关键词答案能不能用?回到前面的教学文本:“市图书馆周末开设儿童阅读课,读者可通过公众号预约。”对照几种输出看看:
| 输出示例 | 检查结果 |
|---|---|
市图书馆;儿童阅读课;公众号预约 | 能概括主要对象、活动和参与方式,是本例的一份参考答案 |
图书馆;活动 | 没有说错,但过于笼统,丢掉了“儿童阅读课”这一主要信息 |
市图书馆;成人培训 | 格式没问题,但原文没有成人培训,内容不正确 |
关键词:市图书馆;儿童阅读课 | 内容与原文有关,但多出了任务不允许的前缀 |
检查一条样本时,可以按这个顺序来:先读原文,自己说出文章主要讲什么;再看参考答案是否抓住主题、有没有添加原文不支持的内容;最后检查分号、前缀和重复关键词。
这就是内容质量与格式质量的区别。分号、空项等问题可以交给脚本检查;关键词是否选得恰当,仍需要对照原文判断。专业文本中的名称拿不准时,应请熟悉该领域的人核对,不能只因为某个词看起来陌生就删掉。
关键词答案也不一定只有一种合理写法。例如,“公众号预约”可以看成一个参与方式,也可以拆成渠道与动作。但同一份数据中不能随意切换口径,下一节会约定本例采用哪种写法。
准备自己的数据时,先审核一小批,确认选词要求可执行,再扩大数量。如果某类文章经常漏掉主题,就补充这类文章及可靠答案;反复复制已经有的样本,并不能补上这个问题。
还要检查:样本是否覆盖实际会收到的文章。 假如训练材料全是图书馆活动通知,换成农业摘要后频繁漏掉品种名,首先要检查这类主题和名称是否得到充分示范。单纯增加更多活动通知,未必能解决问题。
准备自己的数据时:用一张覆盖表决定补什么
下面是一张可用于自己项目的覆盖检查表。同一篇文章可以同时属于“长文”“含专业名称”等多类,不必把它们当成互斥分类。
| 检查维度 | 从数据中找什么 | 对照答案看什么 |
|---|---|---|
| 文章主题 | 实际业务中常见的主题,以及较少见但需要支持的主题 | 是否抓住主要对象与事情 |
| 文本长度 | 短文、较长文章;长度按分词后的 token 数统计 | 关键内容是否落在截断位置之后,见第 30 章第 4 节 |
| 完整名称 | 地名、机构名、活动名、品种名等 | 是否保留影响含义的限定词 |
| 全称与缩写 | 同时包含全称和缩写、只包含缩写的文章 | 是否按同一套规则选词 |
| 文本噪声 | 多余空白、识别错误、残缺句子 | 能否依据现有文本确认答案;疑点是否单独记录 |
为每一类记下“训练样本数量、验证样本数量、已审核数量、主要错误”。发现验证中某类表现差,就回查训练侧是否缺少这类输入或存在答案冲突;从新的来源补充并审核,避免把验证题直接搬进训练集。各类是否都达标,应分别查看,整体平均分可能掩盖少数类别的问题。
需要多少条数据,没有适用于所有任务的固定答案。 本课的 1,600 条训练数据是练习规模。自己的任务应先让常见场景与关键边界有可靠示范,再根据验证结果决定补哪些数据、是否值得扩大训练。样本数量增加,不代表覆盖范围和答案质量一定同步提高。
空文本等不在当前练习范围内的输入,先按第 28 章任务约定确定处理方式。如果需要返回“无法抽取”等新状态,应统一修改任务规则和数据版本,避免同类输入对应相互矛盾的答案。
下面先把输出规则写清楚。
2.3 关键词标注规则
这里的“标注”,指的是为一条输入确定参考答案;“标注规则”就是提前约定什么样的答案才算正确。清洗数据前,先把这套规则写清楚。关键词抽取至少要明确:
| 需要约定的内容 | 本课程当前规则 |
|---|---|
| 输出内容 | 一行,一个或多个能够概括主题的关键词 |
| 分隔方式 | 关键词之间使用英文分号 ; |
| 多余内容 | 不输出解释、序号、“关键词:”前缀或重复词 |
| 关键词数量 | 根据文本主题确定,不强行统一数量 |
不同文章包含的主题信息不同,不必为了统一成“3~5 个”,删掉必要的关键词或凑入无关词。业务确实需要限制数量时,应先制定规则,再按规则审核样本。
格式明确后,还要约定怎样选词。 下面这套口径用于新增标注和审核草案:从给定文本中选取能概括主题的信息,保留原文语言,不自行翻译或补充背景知识。它不是所有关键词任务唯一的规则;如果业务需要英文标签、固定分类词或固定数量,应先另行约定,再一致地准备数据。
先把选词判断落实到三件事:有原文依据,保留完整名称,覆盖文章主题。例如,“儿童阅读课”不能缩成泛泛的“活动”,“公众号预约”在本例作为一项参与方式整体保留。完整规则如下,审核遇到分歧时回来查。
标注时查阅:完整选词规则与分歧处理
| 容易分歧的地方 | 标注时怎样处理 | 例子 |
|---|---|---|
| 选原词还是自己概括 | 优先使用原文词语;可去掉虚词整理成短语,但不改变含义、不翻译、不补充事实 | “通过公众号预约”可整理为“公众号预约”;“儿童阅读课”不改成“亲子教育培训” |
| 哪些信息要选入 | 先保留文章主要讲的对象和事情,再选影响主题理解的方法、结果或参与方式;不是把所有名词都列一遍 | 本例保留图书馆、活动名称和预约方式;“周末”是否也要选,应按业务对时间信息的需要统一约定 |
| 保留完整名称还是缩短 | 保留影响含义的限定词,避免只留下过于宽泛的类别 | 本例选“儿童阅读课”,不缩成“阅读课”或“活动” |
| 一个短语要不要拆开 | 能共同表达一项关键信息的短语,优先整体保留,不为凑数量拆词 | 本例选“公众号预约”,不拆成“公众号;预约” |
| 全称、简称怎样统一 | 原文同时给出全称和简称时,默认选全称;只有简称时保留简称,不自行补全 | 原文写“高层体系结构(HLA)”时选“高层体系结构”;只写“HLA”时保留“HLA” |
| 两个近义词要不要都选 | 表达同一件事时保留一种写法;是否为同义词要结合原文判断 | 不把“儿童阅读课”和自己改写的“少儿阅读课程”同时列入答案 |
| 专业含义拿不准怎么办 | 保留原文和疑问,查来源或请领域人员确认;有未解决的内容疑点时,整条记录先不纳入新增训练数据 | 陌生简称不自行扩写,也不只因看不懂就删除 |
按这套口径,图书馆短文可以标为 市图书馆;儿童阅读课;公众号预约。图书馆;阅读课;公众号;预约 虽然也与原文有关,却缩短了名称、拆开了参与方式,不适合作为这套规则下的标准示范。若业务确实需要把“渠道”和“动作”分别提取,应先调整规则,再一致地标注同类样本。
先走完一个审核示范。 按“对照原文 → 判断问题 → 提出修改 → 保留待确认项”检查下面的地名。它来自验证集第 4 条,用于理解审核方法;修改建议先作为待审核草案。
示例一:名称被截短——“万安县”变成了“安县”。
第 4 条原文节选:
近年来,由于苗木的调运引种和生产上的疏忽,柑桔疮痂病在万安县发生危害有加重趋势
原参考答案中包含 安县。虽然这两个字能在原文中找到,但它们只是“万安县”的一部分。
| 审核步骤 | 本例记录 |
|---|---|
| 判断问题 | 地名被截短,丢掉了具体所指;关键词不能仅凭“能找到这几个字”就判为正确 |
| 提出修改 | 建议恢复为 万安县;按文章主题形成的整条草案为 柑桔疮痂病;万安县;果业生产 |
| 保留待确认项 | 是否同时收录“引种”等背景信息,需要统一选词范围;这不影响恢复完整地名的判断 |
这个示范检查的是参考答案本身。若答案由另一个模型起草,也要经过同样审核;下面两例用验证集第 6 条及已有模型回答练习,不需要先学会第 32 章的评分。
继续练习:从农业样本检查漏主题与无依据编号
示例二:词都有依据,却漏掉了文章主题。
第 6 条原文的两段节选如下,保留原有文字和标点:
夏波蒂是加拿大福瑞克通农业试验站用"F58050"为 母本,"Bakeking"为父本经有性杂交育成
2003年由榆林市农科所弓1人我市试种,通过5年观察,该品种产量高,炸条品质和食用品质优良,适 宜在我市北部风沙滩水地区推广栽培,现将种植表现及栽·培技术简介如下:
一份已保存的模型回答是:
F58050;Bakeking
| 审核步骤 | 本例记录 |
|---|---|
| 判断问题 | 两个词确实在原文中,但都在说明亲本来源。只列它们,无法概括“夏波蒂的种植表现”这一主题 |
| 提出修改 | 应先保留主品种与种植表现。建议草案为 夏波蒂;榆林市;种植表现;推广栽培;炸条品质;食用品质 |
| 保留待确认项 | 是否收录亲本信息需按业务需要约定;原参考中的“马铃薯”未在给定文本直接出现,须查来源确认。原文“弓1人”“栽·培”等疑似文字识别问题也单独记录 |
这里没有断言“马铃薯”一定错误,而是把“当前文本能确认什么”和“还需要查什么”分开。有原文依据,是选词的必要检查,但还要看有没有抓住主题。
示例三:格式正确,却多出了没有依据的词。
还是第 6 条原文,另一份已保存的回答是:
F58050;Bakeking;F68050
| 审核步骤 | 本例记录 |
|---|---|
| 判断问题 | 一行、英文分号、无重复,形式上没有问题;但原文写的是 F58050,没有 F68050,而且仍漏掉“夏波蒂” |
| 提出修改 | 若把它作为新标注的草稿,应排除无依据的 F68050,再按示例二补足主题;只删掉这个词,答案仍不完整 |
| 保留待确认项 | 不能猜测 F68050 是另一个有效编号。若认为输入有错,应查原始资料;确认前不把这份草稿作为训练示范 |
审核训练草稿时可以提出修改;评估模型时必须保留它原来的回答。 例如,不能先替模型删除 F68050,再把修改后的文本拿去评分。
自己审核时,也为每条样本留下“原始答案、建议答案、原文依据、待确认项”。能确定的修改和需要继续查证的疑点分别记录,确认完成后再纳入新增数据。
课程练习数据主要经过格式和重复检查,部分参考标注仍有内容疑点,不能把它们全部视为已人工审核的标准答案。上面的修改建议也需要确认后才能用于新数据。第 32 章的示例分数以随附参考文件为准;第 4.1 节会区分格式是否合格、是否命中参考答案、内容是否合理。
练习:两个人怎样按同一套规则审核
从准备新增的材料中选一小批文章,例如先用 5 条练习。两人分别阅读原文、写出关键词,暂时不看对方答案,再比较完整名称、短语拆分、选词范围和无依据内容。
如果一人写 公众号预约,另一人写 公众号;预约,先回到本节约定,说明为什么此处整体保留;不要只投票决定。遇到规则没有覆盖的情况,补充规则和例子,再用修订后的规则重新检查这批文章。专业事实拿不准时,继续保留待确认项。
记录“样本编号、两份答案、分歧原因、最终处理、规则版本”即可。这一步叫作标注校准,目的是让不同人对规则形成一致理解。一个人学习时,可以先独立作答,再按规则复查;这种自查不能代替独立复核。
3、常见微调数据格式
前面看到的文件名以 .jsonl 结尾,内容又被称为 ShareGPT,后面还会出现 Chat Template。它们处理的不是同一件事:
| 要解决的问题 | 对应名称 | 本课程中的例子 |
|---|---|---|
| 多条记录怎样放进文件 | JSONL | 一行保存一条完整的 JSON 记录 |
| 每条记录怎样保存指令、输入和答案 | Alpaca、ShareGPT | 三个独立字段,或一组按角色排列的消息 |
| 消息怎样组织成模型熟悉的输入 | Chat Template(对话模板) | 加入用户、助手的边界标记,第 6 节展开 |
因此,一个文件可以同时是“JSONL 文件”和“ShareGPT 数据”,两者并不冲突。先看一条记录的两种常见写法。
| 格式 | 一条数据怎样保存 | 更适合的情况 |
|---|---|---|
| Alpaca | instruction、input、output | 单轮的“指令 + 输入 + 答案”任务 |
| ShareGPT | 一组按角色排列的消息 | 用统一的消息结构保存单轮或多轮对话 |
3.1 Alpaca 数据格式
Alpaca 格式会把任务说明、具体输入和标准答案拆开保存。仍用第 1 节的图书馆教学示例:
{
"instruction": "请提取关键词,只输出关键词,并使用英文分号分隔。",
"input": "市图书馆周末开设儿童阅读课,读者可通过公众号预约。",
"output": "市图书馆;儿童阅读课;公众号预约"
}
instruction 是任务要求,input 是这次要处理的文章,output 是参考答案。这种拆法直观,适合单轮任务。
3.2 ShareGPT 数据格式
同一条教学样本也可以按本课程采用的 ShareGPT 风格保存:
{
"conversations": [
{
"role": "user",
"content": "市图书馆周末开设儿童阅读课,读者可通过公众号预约。\n请提取关键词,只输出关键词,并使用英文分号分隔。"
},
{
"role": "assistant",
"content": "市图书馆;儿童阅读课;公众号预约"
}
]
}
任务和答案都没有改变,只是把指令与文章放进 user.content,把答案放进 assistant.content。最外层的 conversations 是消息列表:
conversations
├── 第 1 条:role = user → 待抽取的文本和任务指令
└── 第 2 条:role = assistant → 标准关键词答案
上面为了讲解而展开换行;保存为 JSONL 时,整个对象要放在一条物理行中,消息内部的换行用 \n 表示。不要把展开后的十几行当作十几条训练样本。
如果以后有多轮任务,也可以在同一条样本里继续追加消息:
user1 → assistant1 → user2 → assistant2
本课程的关键词数据是一问一答。后面使用的清洗脚本也按这种单轮结构检查;换成多轮数据时,需要相应调整检查逻辑,不能直接套用。
字段名不一定永远叫 conversations、role 和 content。有的数据把用户写成 human,有的数据把消息列表写成 messages。这不是数据一定有问题,而是训练工具需要一份“字段对照表”才能正确读取它。第 31 章第 6.2 节会在 dataset_info.json 中填写这份对照表。
Alpaca 和 ShareGPT 用来整理样本;训练工具读取这些样本后,还要通过与模型匹配的对话模板和分词器准备模型输入。第 6 节会展示转换前后的区别。
3.3 从单轮问答到工具调用(选读)
关键词任务只需要“读文章 → 输出关键词”。如果希望助手查询儿童阅读课还有没有名额,它还需要判断信息是否齐全、请求查询,并根据查询结果回答。训练样本也要包含这些可观察的步骤。
进阶阅读:从澄清到工具返回的一条完整样本
下面是虚构的教学示意,用于理解一条多轮样本,不是已执行的工具记录,也不是可直接上传训练的文件。本例提供 query_reading_class 工具,只查询市图书馆儿童阅读课的余位,接收两个参数:date 使用 YYYY-MM-DD,time_slot 使用 morning(上午)或 afternoon(下午)。
| 顺序 | 谁提供内容 | 消息或动作示意 |
|---|---|---|
| 1 | 用户 | 儿童阅读课还有名额吗? |
| 2 | 助手 | 你想查询哪一天、上午还是下午的场次? |
| 3 | 用户 | 2026 年 9 月 12 日上午。 |
| 4 | 助手 | 请求 query_reading_class,参数为 date="2026-09-12"、time_slot="morning" |
| 5 | 工具 | 程序执行查询后返回 remaining=6,表示还有 6 个名额 |
| 6 | 助手 | 查询结果显示,2026 年 9 月 12 日上午的儿童阅读课还有 6 个名额。 |
这里最关键的是第 4、5 步的区别:模型提出调用请求,程序负责实际执行,再把结果交还给模型。 助手不能自己写出一个 remaining=6,就当作查到了真实余位。保存整条消息链及工具说明,才有条件让模型学习“何时澄清、怎样调用、如何使用结果”。
准备这类数据时,还需要覆盖不同情况:
| 情况 | 应有的示范 |
|---|---|
| 用户一开始就给齐日期与场次 | 直接填写已有信息,避免重复询问 |
| 用户已经贴出通知,只要求提取关键词 | 根据已给文本回答,不为使用工具而调用工具 |
| 查询返回“场次不存在”或暂时失败 | 按实际结果说明情况;若允许重试,遵守次数和退出条件 |
| 用户信息不足 | 先澄清,不猜测日期或场次 |
落到训练文件时,还要按所用工具核对消息字段、工具参数说明(schema)、调用与返回的对应关系,以及哪些助手消息参与监督。可参考 SFT 工具调用数据说明。本章的单轮关键词清洗脚本不能直接校验这种多轮数据,字段映射和聊天模板也需要相应验证。
如果用另一个模型起草轨迹,调用结果应由真实执行或明确标注的模拟环境核验,不能把生成的成功描述当作执行证据。后续评估还应分别检查工具是否选对、参数是否正确、结果是否被正确使用,见第 32 章。只训练关键词抽取,并不能证明模型已经学会这些行为。
继续本课主线时,仍使用单轮关键词数据;下面的文档问答也是独立扩展,不把三种任务混到同一次练习中。
4、用 Easy Dataset 制作问答数据
独立扩展:手里有文档,还没有问答样本时阅读。 继续关键词主线的读者,可直接进入第 5 节“关键词数据清洗与划分”。本节制作的问答仅用于数据制作练习,不混入后续关键词训练。
假设你要为“青禾文档台”制作客服问答数据,现在手里只有一份产品说明。下面用 Easy Dataset 从说明中生成问题和答案,对照原文审核,最后导出一份 JSONL 文件。
这是一个虚构产品。以下使用 Easy Dataset 1.7.3 演示,随附结果包含 6 条审核后的问答;自己跟做时,生成的问法和数量可能不同。
4.1 文档准备与任务说明
在本地课程目录中找到并阅读 案例与源码-4-微调/document-qa-demo/product-manual.md,稍后将这份文件上传到 Easy Dataset。
文档只有成员管理、文件上传、删除恢复、任务导出和汇总通知五部分。跟做时只需准备这一份文档,问题和答案会在 Easy Dataset 中生成。
以扫描件的上传要求为例,我们希望从文档中整理出这样的问答:
问题:扫描版 PDF 上传前需要做什么?
答案:先完成 OCR,检查识别出的文字是否准确,再上传处理。
换成自己的文档时,先去掉重复页眉、导航等无关内容,保留数值、单位、权限和例外条件。扫描件先做 OCR 并检查文字;内部资料还需确认是否允许发送给所用的模型服务。
4.2 创建项目并配置模型
先打开工具。 本节使用 Easy Dataset 1.7.3 的浏览器界面。已有工具可直接创建项目;首次使用可从官方发布页选择适合自己系统的桌面客户端,或按下面的步骤从源码运行。
首次安装:从源码构建并启动浏览器界面
先确认本机已安装 Git、Node.js 和 npm,在准备存放工具源码的位置打开终端。
下面按 Easy Dataset 官方安装说明安装 1.7.3 标签的源码。示例环境为 macOS(Apple Silicon)、Node.js 20.20.2、npm 10.8.2;先用 node -v、npm -v 查看自己的版本。
git clone --branch 1.7.3 --depth 1 https://github.com/ConardLi/easy-dataset.git
cd easy-dataset
git describe --tags --exact-match
npm install
依赖安装完成后,执行构建:
npm run build
排错:Mac 首次构建提示 Schema engine error
如果首次初始化数据库时只显示 Error: Schema engine error:,可以在同一目录开启数据库引擎日志后重试:
RUST_LOG=info npm run build
RUST_LOG=info 设置数据库引擎的日志级别,便于获取更多报错信息;它不是数据库修复开关。重试后仍然失败时,根据新的日志排查,不要反复执行或删除已有数据库。
构建成功后,再启动服务:
npm run start -- --hostname 127.0.0.1
终端出现 Ready 后,保持终端运行,在同一台电脑的浏览器打开 http://127.0.0.1:1717。--hostname 127.0.0.1 让这套练习服务只接受本机连接。
再创建项目。 创建“青禾文档台 · 文档问答练习”项目,在描述中写清资料来源和用途。

点击页面顶部的 「更多 → 项目设置」,再切换到 「模型配置」,填写服务提供的接口地址、API Key 和模型 ID。保存后,打开 「更多 → 模型测试」,选中刚配置的模型,发送一个简单问题。

测试时选中已配置的模型,收到正常回答后再继续制作数据。若报鉴权失败、模型不存在或连接失败,先修正接口配置。
这里配置的模型负责生成问答草稿,可以与后续微调的模型不同。API Key 不要放进截图或课程文件;生成费用按所用服务计收。没有可用接口时,可以先对照随文结果学习。

图中的接口类型与模型名称仅用于演示配置位置。OpenAI 标签表示接口类型,不等于模型由 OpenAI 提供;跟做时填写自己服务支持的模型 ID。收到回答只能确认接口能用,生成的问答仍要对照原文审核。
4.3 导入文档并检查分块
打开顶部的 「数据源 → 文件处理」,在「上传新文件」区域点击「选择文件」,选中 product-manual.md,再点击「上传并处理文件」。

工具会把文档处理成供模型阅读的文本块,也就是一段相对完整的资料。处理完成后,右侧出现已上传的文档,下方显示文本块 product-manual-part-1,页面计数为 471 字。
点击文本块卡片右下角的蓝色眼睛图标,鼠标停上去会显示「查看详情」,点击后可以阅读处理后的完整正文。

此处先看“查看详情”入口。首次导入后,卡片可以尚无问题;图中的“已生成 6 个问题”属于后续生成状态,不是本步骤的完成条件。

这份说明很短,五部分内容保留在同一个块里即可。长文档则尽量按完整小节拆分,别把“支持 PDF”和“单文件不超过 20 MB”等关联条件拆散,否则生成的答案容易漏掉限制。
4.4 生成问题和答案
打开 「更多 → 项目设置 → 任务配置」,找到「问题生成设置」,将第一个滑块调整到 「100 个字符生成一个问题」,将下方的 「并发限制数量」设为 2,最后到页面底部点击「保存任务配置」。

问题生成密度用于控制希望生成多少问题;并发为 2 表示最多同时处理两项任务。实际使用时,可根据文档长度和模型服务的限制调整。
回到文本块列表,勾选 product-manual-part-1,点击「批量生成问题」。进入「问题」页,可以查看生成的问题及其来源文本块。下面这 6 题分别涉及成员人数、文件限制、回收站、任务导出、邮件汇总和扫描件处理。

先删除重复或偏离文档的问题,再点击每题右侧的「生成数据集」,为问题生成答案。这里每题生成一次即可;任务尚未完成时先查看状态,不要连续重复点击。
4.5 对照原文审核答案
进入「数据集 → 单轮问答数据集」,打开一条记录的「查看详情」,通过「文本块」入口回看原文。重点检查答案有没有改数字、漏条件,或添加没有依据的结论。例如,“最多 5 人,包含所有者”不能变成“所有者之外再加 5 人”。
下面这份扫描件问答草稿多写了一句:
……才能确保后续的处理和问答能够正常进行。
原文只要求先做 OCR、检查识别文字、再上传,并没有作出这个保证。点击答案旁的编辑图标,将答案改为:
扫描版 PDF 上传前,先完成 OCR(光学字符识别),检查识别出的文字是否准确,再上传处理。
保存后点击「确认保留」。修改后的答案如下:

其他答案也按同样方式核对。完成后回到列表,本例 6 条问答都显示「已确认」,可以进入导出步骤。

“已确认”是人工审核状态,不是自动质量评分。正式业务数据应由熟悉业务的人核对。
4.6 导出并检查问答数据
点击数据集列表上方的「导出」,选择 JSONL 和 ShareGPT,勾选「仅导出已确认数据」,取消「包含思维链」,系统提示词先留空。本例只保留问题和审核后的答案,不导出模型生成答案时的分析文字。

点击「确认导出」,浏览器会下载一个 .jsonl 文件。课程保存的 案例与源码-4-微调/document-qa-demo/product_qa_reviewed.jsonl 可供对照。
在编辑器中打开文件。本例共 6 行,每行是一组问答;为了看清字段,下面将扫描件这一行展开显示,文件中仍是一行一条记录:
{
"messages": [
{ "role": "user", "content": "扫描版PDF在上传处理前需要完成哪些操作?" },
{
"role": "assistant",
"content": "扫描版 PDF 上传前,先完成 OCR(光学字符识别),检查识别出的文字是否准确,再上传处理。"
}
]
}
这里的 messages 保存一组对话,role 表示角色,content 保存内容:user 对应问题,assistant 对应审核后的答案。与关键词文件的 conversations 相比,消息列表的字段名不同,里面仍然是角色和正文;交给训练工具时,需要登记实际使用的字段名。
导出后检查三件事:
- 条数是否对应。 本例确认保留了 6 条,文件中也应有 6 条;自己跟做时,以实际保留的数量为准。
- 问答是否完整。 每条记录都有问题和答案,角色没有写反,内容没有空缺。
- 修改是否保存。 找到刚才修改的扫描件答案,确认文件里是修改后的文字,没有未保存的旧答案,也没有额外的思维链内容。
如果文件还是修改前的答案,回到工具中检查是否保存、确认了该条记录,再重新导出;条数不符时,检查「已确认」状态和导出筛选条件。
至此,文档问答练习的产物是审核后的 JSONL。继续本课时,回到关键词 small 文件完成下一节;这份问答文件单独保留,后续关键词训练仍使用关键词数据。
5、关键词数据清洗与划分
按照第 2 节的标注规则整理关键词样本,再把它们分成训练、验证和测试三份。本节的数据处理在本机完成。
5.1 常见问题与处理方法
数据清洗,就是把不适合直接训练的记录整理好。 能确定的格式错误可以批量修正;答案内容有问题,则要对照输入重新标注。不能修正、又无法确认的记录,先不放进训练数据。
下面用几个教学示例说明处理方法:
| 问题 | 示例 | 处理方法 |
|---|---|---|
| 多余前缀、中文分号和尾部分号 | 关键词:图书馆;阅读课; | 改为 图书馆;阅读课,保留关键词内容 |
| 同一答案重复列词 | 图书馆;阅读课;图书馆 | 删除重复词,保留 图书馆;阅读课 |
| 输入或答案为空 | 有 user,但 content 没有正文 | 核对来源,补齐后审核;无法补齐则暂不使用 |
| 答案缺少依据 | 输入只讲儿童阅读课,答案却有“成人培训” | 按当前输入重新标注,不能只修分号 |
第 2 节已经讲过内容审核。这里要特别区分两种“重复”:上表第二行是在一个答案中重复列词;下面则是同一条样本出现多次。
输入 A → 图书馆;阅读课
输入 A → 图书馆;阅读课
相同输入、相同答案,保留一条即可。如果同一输入对应的答案不同,就不能直接按重复记录删除:
输入 A → 图书馆;阅读课
输入 A → 图书馆;公众号预约
这叫答案冲突。可能是某份标注漏了内容,也可能是两种表达都合理,需要结合文章和选词规则判断。课程脚本遇到这种情况会停止,不会随便留下第一条。
这些处理应在划分之前完成,否则同一道题可能同时进入训练集和测试集。清洗与后续评分共用格式检查规则;关键词内容仍需按第 2.2 节的方法对照原文审核。
5.2 训练集、验证集和测试集
第 28 章介绍过三份数据的用途。放到关键词练习中,可以把它们理解为“练习、模拟考试、期末考试”:
| 集合与文件 | 类比 | 是否更新参数 | 怎样使用 |
|---|---|---|---|
训练集 keywords_train.jsonl | 日常练习册 | 是 | 让模型学习关键词抽取 |
验证集 keywords_validation.jsonl | 备考期间的模拟考试 | 否 | 比较配置、观察表现、选择检查点 |
测试集 keywords_test.jsonl | 最后才拆封的期末考试 | 否 | 方案确定后,比较原始模型与微调模型 |
三份文件都保留参考答案。checkpoint(检查点) 是训练时保存的阶段存档,例如训练一轮、两轮后分别保存一个版本,再用验证集比较哪个更合适。
5.2.1 泛化与过拟合
我们训练模型,不是为了让它只会回答这 1,600 条文章,而是希望以后换一篇同类文章,它也能按要求抽取关键词。这种把学到的规律用到未参与训练的新输入上的能力,叫作泛化能力。
沿用第 1 节的图书馆例子。假设模型练习过第一篇文章,现在用第二篇检查它。下面是教学示例,不是课程数据或模型实测结果:
| 用途 | 文章 | 参考关键词 |
|---|---|---|
| 训练时练习 | 市图书馆周末开设儿童阅读课,读者可通过公众号预约。 | 市图书馆;儿童阅读课;公众号预约 |
| 换一篇同类文章检查 | 区图书馆暑假举办少儿科普讲座,家长可在小程序中报名。 | 区图书馆;少儿科普讲座;小程序报名 |
两篇都要求抽取关键词,但活动、参与方式变了。模型需要根据新文章选词,不能照搬“儿童阅读课”和“公众号预约”。如果它能在多篇这样的新文章上抓住主题、遵守分号格式,才说明学到的做法能用于新输入。
过拟合则是模型过分贴合训练样本,在没有参与训练的同类数据上表现不好。可以类比成反复刷熟一套题,原题答得很好,考查内容相同、条件稍有变化的新题却不会做。
例如,继续训练后,模型对练习过的文章选词更准确了,对留出的新文章却经常漏掉主题、带入训练题中的词。如果这种情况在多条样本上持续出现,就需要警惕过拟合,而不能只看练习题答得越来越好。
不过,一道新题答错不等于过拟合。参考答案有误、文章被截断,或者新文章明显超出了训练数据覆盖的范围,也可能导致回答不好。需要结合多条样本和训练过程判断,不能看到一个错误就认定“训太多了”。
这也解释了验证集为什么要留在训练集之外:训练过程中用它观察模型处理新文章的表现,再决定是否继续训练、选择哪个阶段的存档。具体怎样结合训练与验证曲线判断,见第 30 章“过拟合的判断与处理”。
5.2.2 数据泄漏
不要让测试题混进训练集。 完整关键词文件包含 small 中的样本,因此不能“用完整文件训练,再用 small 测试”。测试题已经被练过,这是一种数据泄漏,分数不能代表模型处理新文章的能力。
验证集也一样:用来检查的文章如果已经参与过训练,就不能再把它的表现当作处理新文章的证据。过拟合说的是模型学得怎样,数据泄漏说的是检查过程出了问题;两者不是同一件事,数据泄漏还可能掩盖模型在新文章上的问题。
同一篇文章换一种问法,也不一定是独立的新材料。课程脚本只比较合并连续空白后的完整输入,不能识别所有近似样本。对于第 4 节那类文档问答,应按来源文档分组,同一文档的关联问答不要随意拆到训练和测试两边。
本例按 80% / 10% / 10% 划分,比例可以根据任务调整。如果反复根据测试结果修改参数,测试集也参与了开发,之后需要另备独立测试数据。
5.3 运行数据处理脚本
现在使用 prepare_keywords_dataset.py 完成关键词数据的检查、清洗与划分。
脚本按顺序完成:
- 检查 JSON、
conversations、user → assistant顺序以及非空内容。 - 统一关键词前缀、分号、首尾空白,去掉空项和重复关键词。
- 按输入去重;同一输入对应不同答案时停止,交给人工核对。
- 对处理后的样本进行固定划分,并检查三份数据的输入互不重复。
在代码编辑器中打开课程项目,在项目根目录新建终端,进入案例目录并确认 Python 为 3.10 或以上版本:
cd "案例与源码-4-微调"
python3 --version
如果终端已经位于案例目录,就不必再次执行 cd。接着运行:
python3 prepare_keywords_dataset.py \
--source keywords_data_sharegpt_small.jsonl \
--seed chapter-29-v1 \
--output-dir processed/keywords-clean-repro
| 参数 | 作用 |
|---|---|
--source | 指定要处理的关键词文件 |
--seed | 固定分组所用的种子 |
--output-dir | 指定一个尚不存在的结果目录 |
种子可以理解为分组时使用的约定值。数据、处理规则和种子都不变时,每条输入的排序与分组才会保持一致。本例使用 chapter-29-v1 作为固定种子,跟做时保持这个值即可。
本次练习另存到 keywords-clean-repro/,让你核对自己完成的处理过程。课程后续训练示例固定使用附带的 keywords-clean/,两者的用途在下一节对照。脚本遇到已存在的输出目录会停止,避免误覆盖。
脚本使用本机 Python 处理文件,无需联网或显卡。它只适用于单轮关键词数据,不能拿第 4 节的自然语言问答按分号规则清洗。
5.4 查看处理结果
正常完成后,终端会列出三份数据的条数。主要输出如下:
keywords_train.jsonl: 1600 条
keywords_validation.jsonl: 200 条
keywords_test.jsonl: 200 条
这是一张处理流程图,展示脚本与三份数据、配套报告的关系。运行时使用第 5.3 节写明种子的完整命令。
在编辑器中展开 processed/keywords-clean-repro/,检查生成的六个文件:
- 三个 JSONL 是实际数据。分别打开一条,仍能看到
user输入和assistant参考答案,格式并没有因为划分而改变。 dataset_info.json是数据集登记表,供第 31 章的 LLaMA-Factory 读取。cleaning_report.json保存自动修改和待检查项目;manifest.json保存来源、划分规则和条数,供核对处理结果时查阅。
如果脚本中途停止,按报错检查:
| 提示或现象 | 处理方法 |
|---|---|
| 找不到输入文件 | 检查终端所在目录和 --source 路径 |
| JSON、角色顺序或空内容错误 | 打开提示的行,修正样本结构与内容 |
| 相同输入的答案冲突 | 对照输入审核答案,不让脚本替代判断 |
| 只生成清洗报告,没有三个 JSONL | 查看报告中的 review_required,修正剩余格式问题后另存新目录重试 |
| 输出目录已存在 | 换一个新目录名,避免覆盖已保存的结果 |
脚本完成只说明通过了这些结构、格式和重复检查,不代表每条答案都经过人工审核。换用自己的数据时,还应抽查文章与关键词是否对应、同源或近似样本是否跨组。
自己的处理结果怎样接到后面? 先按当前练习选择文件,避免把不同版本混在一起:
| 当前在做什么 | 使用哪份产物 | 下一步 |
|---|---|---|
| 学习清洗与划分 | 自己生成的 processed/keywords-clean-repro/ | 核对三份数据的条数、样本、清洗报告和划分记录,确认自己完成了哪些检查 |
| 复现本课程训练 | 附带的 processed/keywords-clean/ | 第 31 章上传这一目录;训练用 train、过程检查用 validation,第 32 章再用 test |
| 改用自有数据 | 审核后另存的新数据版本及配套登记表 | 按第 31 章登记自己的文件,训练与评估始终对应同一版划分;新实验单独保存 |
两个目录名称不同,本身不能证明内容相同。若要用自己的复算数据替代课程数据,应先对照处理规则、三份 JSONL 和登记内容;不要只看总条数。预测结果另外保存,参考答案继续留作比较依据。
6、对话模板与模型输入
上一节得到的数据仍是一条条 user 输入和 assistant 参考答案。接下来看:训练工具读取这些消息后,怎样把它们交给模型。
6.1 对话模板的作用
继续用第 1 节的图书馆例子。我们能通过 role 分清哪段是用户的问题、哪段是助手的答案,模型也需要识别这些边界。
Chat Template(对话模板)就是组织消息的规则:怎样标出用户消息的开始和结束,怎样标出助手回答的位置。不同模型使用的规则可能不同,因此训练工具需要选择与模型匹配的模板。
这个概念并不只存在于代码里。第 31 章要使用的 LLaMA-Factory 页面,就有一个“对话模板”选项:
模型路径决定加载哪份模型,对话模板决定怎样组织消息。
它与第 13 章的提示词和消息模板分工不同:之前是在填写任务说明、变量和对话内容;这里是按模型要求,给消息加上角色边界。
6.2 查看模板与转换结果
先找到模板规则。 第 28 章介绍过模型目录中的权重、配置和分词器文件。以本课程的 Qwen/Qwen3-0.6B 为例,打开 tokenizer_config.json,搜索 chat_template,就能找到模型发布者提供的对话模板。
暂时没有下载模型,也可以打开官方模型文件页,用浏览器查找 chat_template。对照下图找到文件名和模板字段:
这一项保存了不同角色和条件的处理规则,由工具读取使用。
再看同一条消息转换前后有什么变化。 下面使用模型自带的官方模板处理第 1 节的图书馆教学样本,关闭思考模式,观察消息的组织方式。
对照左右两边,文章和关键词答案都还在,变化主要是消息的边界表示:
<|im_start|>user:用户消息开始,后面接文章与任务说明。<|im_end|>:这一条消息结束。<|im_start|>assistant:助手消息开始,后面接参考答案。
这里的关键词来自样本中的 assistant.content;图中的空 <think> 区块由官方模板添加。模板负责组织已有消息,答案质量仍由数据审核保证。
遇到回答不结束时再看:消息边界、思考结束与 EOS
还要区分两种“结束”:</think> 表示思考区结束,后面仍可继续写答案;消息结束标记则用于划分消息。推理程序还需要配置生成停止条件,识别何时结束助手的输出。常见的 EOS(End of Sequence,序列结束标记)以 token 编号参与这项判断,具体编号和用途要与模型、模板及推理引擎对应,不能把 </think> 直接当成回答结束。第 32 章再结合重复输出与生成停止检查实际现象。
本图用官方模板说明转换过程;实际训练以训练工具的预处理结果为准。 第 31 章使用 LLaMA-Factory 的 qwen3_nothink,与官方模板的处理可能不同,不能直接沿用本图的 token 数量。第 30 章第 4.2 节会带你核对实际输入。
最后,把文字变成编号。 前面提到的 Tokenizer(分词器)会把文本和标记转换为模型可以计算的编号。每个基本单位叫 Token(词元),不一定对应一个汉字。
例如,用同一份 Qwen3 分词器单独处理“市图书馆”,得到:
| 分词后可读的内容 | 对应编号 |
|---|---|
| 市 | 22697 |
| 图书馆 | 106036 |
四个汉字在这个例子里是两个 token,对应输入编号 [22697, 106036]。<|im_start|> 这样的特殊标记也有自己的编号。input_ids 指的就是模型的输入编号序列。
模型输入的转换过程是:从 JSONL 读出消息 → 按模板组织消息 → 转成 token 编号。 第一环节还需要告诉工具去哪个字段找消息,这叫字段映射;第 31 章登记数据集时再实际填写。后两步通常可以由工具一起完成,不需要另外保存一份中间文本。
6.3 训练与推理的输入区别
同一条图书馆消息,准备训练样本和实际向模型提问时,输入并不相同:
| 对比项 | 准备训练样本 | 准备一次推理 |
|---|---|---|
| 提供哪些消息 | user 输入和 assistant 参考答案 | 只提供 user 输入 |
| 助手位置放什么 | 已有的参考答案及结束标记 | 回答的起始位置,等待模型续写 |
前面的用户文章相同,重点比较助手部分的末尾。下面两段都是官方模板的实际处理结果;显示出来的空行也是模板的一部分。
训练样本包含参考答案:
<|im_start|>assistant
<think>
</think>
市图书馆;儿童阅读课;公众号预约<|im_end|>
推理输入停在准备回答的位置:
<|im_start|>assistant
<think>
</think>
模型接下来才从这里生成关键词。实际提问时,不能先把参考答案塞进输入,再把后续输出当成模型独立答对的结果。
代码复现:打印模板结果与分词编号
完成第 31 章的环境准备与模型下载后,可运行下面的代码查看模板输出。
在 AutoDL 的 JupyterLab 文件面板中打开 /root/autodl-tmp/LLaMA-Factory/,新建文本文件并命名为 preview_chat_template.py,将下面的 Python 代码粘贴进去并保存。把 model_dir 换成下载完成时返回的实际目录,该目录需要包含 tokenizer_config.json 和分词器文件。下面只加载分词器,不加载模型权重,也不启动推理或训练。
from pathlib import Path
from transformers import AutoTokenizer
model_dir = Path("/root/.cache/modelscope/models/Qwen--Qwen3-0.6B/snapshots/master")
tokenizer = AutoTokenizer.from_pretrained(model_dir, local_files_only=True) # 只读取本地分词器文件
messages = [
{
"role": "user",
"content": "市图书馆周末开设儿童阅读课,读者可通过公众号预约。\n"
"请提取关键词,只输出关键词,并使用英文分号分隔。",
},
{"role": "assistant", "content": "市图书馆;儿童阅读课;公众号预约"},
]
for name, chat, generation_prompt in [
("训练样本", messages, False),
("推理输入", messages[:1], True),
]:
text = tokenizer.apply_chat_template(
chat,
tokenize=False, # 返回模板处理后的文字,方便观察消息边界
add_generation_prompt=generation_prompt, # 推理时补上助手回答的起始位置;训练样本不补
enable_thinking=False, # 关闭思考模式
)
token_ids = tokenizer.apply_chat_template(
chat,
tokenize=True, # 将模板处理后的文字转换为 token 编号
return_dict=False, # 直接返回编号列表,便于用 len() 统计长度
add_generation_prompt=generation_prompt,
enable_thinking=False,
)
print(f"\n{name},共 {len(token_ids)} 个 token:")
print(text)
ids = tokenizer.encode("市图书馆", add_special_tokens=False) # 不额外添加特殊标记,只观察短语本身
print("短语编号:", ids)
print("逐个解码:", [tokenizer.decode([token_id]) for token_id in ids])
保存后,新开一个 AutoDL 的 JupyterLab 终端,执行:
cd /root/autodl-tmp/LLaMA-Factory
source .venv/bin/activate
python preview_chat_template.py
终端会依次打印「训练样本」「推理输入」和短语的分词结果。若提示找不到文件,检查 Python 文件的保存目录;若找不到分词器,核对 model_dir。
同一组消息调用两次模板:第一次看文字,第二次看编号。return_dict=False 明确要求返回列表,因此 len(token_ids) 统计的是 token 数量。参数说明见 Transformers 官方文档。
示例条件: Transformers 4.57.6、Qwen3-0.6B 版本 c1899de 的官方分词器文件。图书馆样本的训练输入为 51 个 token,推理输入为 40 个 token,均包含文章与模板标记;空 <think> 区块由该官方模板在关闭思考时加入。
第 31 章使用 Transformers 5.8.0,运行时以自己加载的分词器和输出为准,不必为对齐这里的数字更换训练环境。官方模板演示与工具训练输入的区别见第 6.2 节;实现细节可查 LLaMA-Factory 模板源码。
如果想观察课程里的真实样本,可以在循环之前,用下面代码替换教学用的 messages,其余部分保持不变:
import json
data_file = Path(
"/root/autodl-tmp/LLaMA-Factory/data/keywords-clean/keywords_train.jsonl"
)
with data_file.open(encoding="utf-8") as source:
record = next(json.loads(line) for number, line in enumerate(source, 1) if number == 484)
messages = record["conversations"]
这条记录是第 1 节图片展示的“高氟铍矿石”样本,在 small 文件中位于第 1 条,划分后位于课程训练集第 484 条。上述固定模板下,训练输入为 139 个 token、推理输入为 121 个 token。
6.4 模板选择与注意事项
后续关键词练习在 LLaMA-Factory 中选择 qwen3_nothink,并关闭思考设置。先完成下面三项检查;实际回答是否符合要求,到第 32 章再验证。
- 模型与模板要匹配。 更换模型后重新核对模板,训练与推理时保持匹配。
- JSONL 中保留正常消息。 不要把上面打印的整段模板文本塞回
user.content,也不要手工补上角色标记,否则工具处理时可能重复包装。 - 检查训练工具的实际输入。 训练前检查预处理后的长度、角色和答案位置,方法见第 30 章“检查实际训练长度”。
关闭思考设置仍不保证没有分析段。课程固定版本中,两种模板添加空思考区块的方式不同;遇到这一现象,再读第 32 章的模板对照,无需在本章先排查推理细节。
章节思考题:
- JSON 和 JSONL 在文件保存方式上有什么区别?把一个样本展开成十几行展示,保存时就变成了十几条训练数据吗?
参考思路: JSON 表示一个完整的 JSON 值;JSONL 则要求每个非空物理行分别是一个完整 JSON 值,本课每行保存一个样本对象。讲解时展开换行仍是同一个样本,写入本课 JSONL 时应放回一条物理行;消息内部的换行用转义形式保存,不能按展示行数计算样本数。
- 同一条“文章 → 关键词”样本使用 Alpaca 和 ShareGPT 保存时,任务要求、文章和参考答案分别放在哪里?
参考思路: 本课 Alpaca 示例将任务要求放在 instruction、文章放在 input、参考答案放在 output。ShareGPT 示例将任务与文章放在 user 消息,把参考关键词放在 assistant 消息,并用 conversations 列表组织消息。改变的是组织方式,任务与答案不变;多轮消息仍应保持在同一条样本的对话中。
- 登记表中的字段映射、Chat Template 和 Tokenizer 分别负责什么?一份能解析的 ShareGPT 数据怎样成为模型输入?
参考思路: 字段映射告诉训练工具去哪里读消息、怎样识别角色;Chat Template 按模型要求组织角色标记、内容和对话边界;Tokenizer 再把文本转换成 token 编号。文件能解析只完成了读取的前提,还要确认角色映射正确、模板与模型匹配,以及转换后的输入符合预期。
- 训练时会提供参考关键词,独立推理时应提供哪些内容?怎样检查自己没有把答案提前放进输入?
参考思路: 训练序列包含任务、文章和助手参考答案,用于后续计算学习目标。独立推理只提供需要的任务与上下文,在助手待回答的位置开始生成,参考答案另存用于比较。检查第 6.3 节展示的转换结果,确认推理输入没有参考关键词,也没有把已经套好的模板再次包装进用户消息。
- 图书馆原文写的是儿童阅读课,参考答案却写成“市图书馆;成人培训”。分号正确,为什么仍要修订?两位标注者对“公众号预约”是否拆开有分歧时怎样处理?
参考思路: “成人培训”没有原文依据,属于内容错误;分隔符正确不能弥补它。短语拆分要按第 2.3 节的标注规则统一口径,说明本例整体保留的依据,再复查相关样本。脚本适合检查结构和格式,专业含义与标注分歧仍需人工审核。
- 训练集、验证集和测试集分别用于什么?比较训练一轮还是三轮时用哪份数据,最终检查又用哪份?
参考思路: 训练集参与参数更新,验证集用于比较训练方案和选择检查点,测试集用于方案确定后的独立检查。本例按 80%/10%/10% 划分,但比例要随任务确定。若测试结果已经被反复用于开发,应另备未参与选择的数据,不能靠重新随机划分恢复其独立性。
- 同一篇文章改写出两条不同问法,一条进入训练集,另一条进入测试集,为什么精确去重仍可能发现不了泄漏?
参考思路: 精确去重只能识别其规则覆盖的重复,问法不同可能使两条记录不再相同,但它们仍共享来源和答案信息。应按原文或关联材料分组划分,再检查集合交叉。本课完整数据与 small 文件也有重叠,不能分别当作训练集和独立测试集。
- 训练数据大多是活动通知,而验证时农业摘要经常漏掉品种名,下一批数据应怎样准备?
参考思路: 先对照原文确认错误,检查农业主题、完整名称、长文本等场景是否缺少可靠示范,再补充经过审核的相关样本。若名称标注本身有分歧,先统一规则;重复增加同类通知并不能补齐这些缺口。数据修改后,用验证结果检查目标错误是否减少。
- 清洗、划分完成后,应交给后续训练哪些文件?怎样说明这批数据已处理什么、还有什么待检查?
参考思路: 交接训练、验证、测试三份 JSONL,配套登记表、清洗报告和 manifest。核对文件位置、条数、划分记录、重复与集合交叉,抽看实际样本,并说明自动检查范围、人工审核结果和待处理项。文件生成成功只能证明流程产出了文件,不能证明所有答案都已通过语义审核。
选读练习:文档问答与工具调用
- 用 Easy Dataset 从文档制作问答,为什么要经历“检查分块 → 生成问答 → 对照来源审核 → 导出检查”?
参考思路: 分块影响生成时能看到的上下文,生成结果可能遗漏限制条件或写错事实,所以要回到来源检查数字、条件和答案依据,保存修订后再导出。打开导出文件核对条数、角色和修改后的答案,确认审核结果确实进入了后续数据。
- 工具调用训练样本与普通一问一答有什么不同?只有“查询成功,还有 6 个名额”这句回答,缺少了什么?
参考思路: 还需用户上下文、工具说明、调用名称与参数、对应工具结果和后续回答;必要时包含澄清过程。工具结果应来自真实执行或明确标注且核验过的模拟环境,并覆盖无需调用、调用失败等情况。单轮关键词脚本不能直接校验这些消息之间的对应关系。
本章小结:
- JSONL 规定每行保存一个 JSON 值,本课每行是一条样本;Alpaca 和 ShareGPT 则规定任务、输入、答案或消息怎样组织。文件格式与样本结构要分开理解。
- 训练答案既要能读取,也要有原文依据并符合标注规则。数据应覆盖实际任务的主题、长度和专业名称,格式清洗不能替代语义审核。
- 训练集用于参数更新,验证集用于选方案,测试集用于独立检查。清洗、去重和来源分组共同减少泄漏,精确去重不能发现所有同源近似样本。
- 字段映射帮助工具读取角色和内容,对话模板按模型要求组织文本,分词器将其转换为 token 编号。训练提供参考答案,独立推理只提供待处理内容与必要上下文。
- 清洗后保留三份数据、登记表、清洗报告和 manifest,并核对实际内容。文档问答需要对照来源审核,工具调用还需要检查请求、执行结果与后续回答的对应关系。
建议下一步: 对照第 5.4 节核查生成的文件,抽看三份数据,说明已处理什么、还有什么待审核。后续跟做使用正文约定的 keywords-clean/,自己的复现目录单独保留。带着这份数据说明进入第 30 章,理解模型怎样利用这些答案更新参数。
模型训练原理与高效微调
30 - 模型训练原理与高效微调
本章课程目标:
- 能沿着一条样本解释 token 预测、Loss、梯度与优化器更新,分清输入上下文和直接监督的位置。
- 能计算 batch、梯度累积和 epoch 对有效批次与更新步数的影响,并解释学习率、预热和衰减的作用。
- 能说明训练显存的主要组成,分清数值精度与量化,理解序列长度、截断和梯度检查点的影响。
- 能比较全参数微调、LoRA 与 QLoRA,读懂 rank、alpha、target 和 dropout 的基本含义。
- 能把配置、训练与验证日志、检查点和实际回答联系起来,判断下一步应检查或调整什么。
学习建议: 先跟着分号例子讲清“预测 → 损失 → 梯度 → 更新”,再依次理解批次、学习率、长度、显存与 LoRA。第 8.1 节汇总一次完整训练安排,英文配置字段需要时再查;不用每学一个概念就重新读整张表。章末主线题按这个顺序自测,手算交叉熵与矩阵推导可以后看;读完后应能说明一份配置怎样安排训练,再进入第 31 章实操。
第 29 章已经准备好 1,600 条训练数据、200 条验证数据和 200 条测试数据。接下来,我们让 Qwen/Qwen3-0.6B 根据“文章 → 关键词”的示例,练习按要求提取关键词。
先分清:训练设置、模型权重和观察结果。
训练程序会根据文章和参考答案,调整模型内部参与计算的数值。这些数值叫作模型参数,也叫权重,由程序计算和更新。
我们填写的是训练设置:一次处理几条、整份数据练几遍、每次更新的幅度怎样控制。日常说“调训练参数”,通常就是修改这些设置。后文谈到“更新模型参数”,则是指程序改变内部权重。
程序还会计算 Loss(损失值)、记录训练进度,供我们观察。它们不需要预先填成某个目标数值;例如,“训练 3 轮”由我们设置,“现在完成了多少步”由程序记录。
先认识五项设置。 现在只看它们分别回答什么问题,具体变化在对应小节展开:
| 我们要决定的问题 | 训练设置 | 本课程安排 |
|---|---|---|
| 一次处理几条数据? | 批处理大小(batch size) | 每批 4 条 |
| 处理几批后更新一次模型参数? | 梯度累积步数 | 累积 8 批再更新 |
| 把整份训练数据练几遍? | 训练轮数(epoch) | 练习 3 轮 |
| 每次更新的步子有多大? | 学习率(learning rate) | 按 5e-5 设置,随训练进度调节 |
| 一条训练样本最多保留多长? | 截断长度(cutoff_len) | 2048 个 token,包含输入和答案等内容 |
本课程使用 LoRA:训练时保持原模型权重不变,主要调整新增的一小部分权重。第 6 节介绍它的计算方式,以及怎样减少训练开销。

阅读安排: 正文结合例子解释训练过程和参数用途,手算与推导放在折叠的选读部分。
- 训练原理: 上面五项设置的作用,训练怎样利用参考答案,LoRA 改了哪部分,以及 Loss 为什么不能代替实际回答质量。
- 配置参数: 优化器、预热与调度、LoRA 的 rank 和 alpha、计算精度、梯度检查点等。完整配置在第 8.1 节汇总,实际运行时还需核对精度与显卡的匹配等条件。
- 进阶选读: 交叉熵手算、标签数组、矩阵推导、量化编码和进一步的实验对照。
1、微调训练过程
本节先掌握: 训练围绕“预测 → 损失 → 调整”展开。模型根据输入预测答案,程序对照参考答案计算损失,再调整可训练权重。
沿用第 29 章的图书馆例子,先只看输入和参考答案:
输入:市图书馆周末开设儿童阅读课,读者可通过公众号预约。
请提取关键词,只输出关键词,并使用英文分号分隔。
参考答案:市图书馆;儿童阅读课;公众号预约
第 28 章已经区分过:普通推理使用已有权重,微调会更新可训练权重。现在只跟踪这条样本,看训练程序怎样利用参考答案。
先跟着这条样本走一遍。答案里的“市图书馆”后面应该接英文分号,假设模型在这里还不太倾向于使用分号,训练程序会怎样处理?
- 模型读入文章、任务说明和前面的答案片段,计算下一个 token 的预测。
- 程序用参考答案里的分号检查预测,把模型给正确项的概率换算成一个损失值。
- 程序根据损失求出内部参数的调整方向,再按我们设定的更新规则改变可训练参数。
- 执行更新后,后续样本会使用新参数继续计算。实际可以先累积多批信息再更新,第 2 节会解释这种安排。
文章与参考答案没有被改写,改变的是模型内部用来计算预测的数值。 上面只是跟踪一个位置来理解流程,实际训练会汇总多个位置、多个样本的信息;一次更新也不保证每个位置都预测得更好。下面先看程序怎样衡量预测,再看它怎样更新参数。
1.1 预测结果与损失
在“市图书馆”后面,模型会为许多候选 token 计算概率,英文分号只是其中一项。参考答案要求这里使用分号,因此我们先检查模型给它分配了多少概率。下面比较同一个位置的两种假设预测,不是模型实测输出:
| 对同一个正确 token 的预测 | 分号的概率 | 怎样理解 |
|---|---|---|
| 预测 A | 10% | 模型给正确项的概率较低,这个位置的损失较大 |
| 预测 B | 80% | 模型给正确项的概率较高,这个位置的损失较小 |
程序要用一个可计算的数衡量预测,这个数就是 Loss(损失值)。在这里,给正确 token 的概率越高,算出的损失越小。它提供了训练要努力降低的目标,第 1.2 节再解释怎样据此调整参数。
本课程使用交叉熵(cross entropy)把模型给正确 token 分配的概率换算成损失。
因此,即使两次预测最后都选中了分号,它们的损失也可能不同。交叉熵还会检查“模型给正确项分配了多大的概率”,不只是记一次对或错。
先用于判断:Loss 是预测损失,不是关键词准确率。 它由参考答案中需要学习的多个位置汇总而来。训练使用参考答案中的前文预测后面的 token,具体位置对应关系可展开查看。
进阶选读:预测位置怎样对应,为什么模型不能提前看答案
分号的预测是怎样得到的? 第 29 章已将输入和参考答案组织成训练序列。为便于说明,假设答案开头按 [市] [图书馆] [;] 拆分;实际分词以工具处理结果为准:
| 要预测的位置 | 模型可以使用的内容 | 用来检查预测的参考 token |
|---|---|---|
| 答案开头 | 用户文章、任务说明和助手回答的起始标记 | 市 |
市 之后 | 上述上下文 + 参考答案中的 市 | 图书馆 |
市图书馆 之后 | 上述上下文 + 参考答案中的 市图书馆 | ; |
表中最后一行就是刚才检查的位置。虽然训练序列含有完整参考答案,但模型预测分号时,只能使用它之前的内容,不能看到待预测的分号及其后文。训练框架可以同时计算多个位置,每个位置都遵守这个限制。
训练时,前面的答案片段来自参考答案;实际让模型回答时,它要接着自己已经生成的内容往下预测。因此,训练不是先生成一整段回复,再拿两段文字逐字找不同。
一条参考答案里的文字、分号和结束标记都可能有需要预测的位置。程序计算并汇总需要计入的位置,得到训练使用的损失;哪些位置不计入,下一小节会说明。日志中的 Loss 不是答错了几个词,也不是关键词准确率。 第 9 节再结合实际日志说明汇总范围。
进阶选读:10% 和 80% 怎样变成损失值
本例每个预测位置都有一个确定的参考 token,按普通交叉熵计算,该位置的损失可以写成:
这个位置的 Loss = -ln(正确 token 的概率)
概率 0.1:-ln(0.1) ≈ 2.3026
概率 0.8:-ln(0.8) ≈ 0.2231
ln 是自然对数,一种数学运算。它在这里把概率转换成损失:概率越接近 1,算出的损失越接近 0;概率越小,损失越大。
再假设一条短答案只有两个需要计分的位置,损失分别为 2.3026 和 0.2231。若按这两个位置取平均,得到约 1.2629。实际日志会进一步汇总多个样本和记录区间,见第 9 节。
公式与忽略标签的规则可对照 PyTorch 交叉熵说明。这里用概率解释公式;直接调用 PyTorch 的 CrossEntropyLoss 时,接口接收的是模型尚未转换成概率的原始分数,不要把这段手算当成接口调用代码。
1.1.1 训练标签与损失计算位置
输入中既有文章和任务说明,也有参考答案。那么,每个位置都要直接计算损失吗?
回到这个任务:我们希望模型学会“读文章、提取关键词”,而不是练习续写用户提交的文章。因此,本课程保留用户文章作为理解任务的上下文,主要根据助手答案中的预测位置计算损失。
这由课程配置中的 train_on_prompt: false 控制:用户文章提供上下文,其对应的预测位置不计入损失;助手答案对应的预测位置参与损失计算。下面的标签示例展示了这种区别。
调整时再看:train_on_prompt 改为 true 会改变什么
如果改为 true,用户文章中相应的 token 预测也会计入损失,程序就同时练习续写这部分内容。尤其是文章较长、关键词答案较短时,损失中会多出大量文章位置。训练目标的组成变了,不能拿修改前后两个 Loss 的大小直接判断关键词提取是否改善。本课程保持 false,将直接监督集中在助手答案上。
选读:训练标签 labels、-100 与预测位置怎样对应
训练程序用 labels(训练标签)指定这些位置,它是一串与输入位置对应的目标编号。采用本例的 train_on_prompt=False 设置时,Prompt 对应的标签通常记为 -100,表示计算损失时忽略这里;助手答案位置则保留要预测的目标编号。
把“能看到的上下文”和“用来检查预测的目标”分开看,就能理解为什么文章需要保留,却不一定直接计入损失:
图中按目标 token 的位置标记标签;训练内部会将前一位置的预测与下一个目标对齐。分词、角色边界和结束标记均为教学示意,实际仍需核对预处理结果。
-100 只标记忽略损失的位置,用户文章仍完整保留在 input_ids 中。 文章依然参与计算、占用序列长度,帮助模型理解任务。
第 29 章打印的模板文本只展示输入形式,没有展示训练标签。要确认实际哪些位置参与损失计算,还需要检查 LLaMA-Factory 预处理得到的 input_ids 和 labels,不能仅凭模板名称判断。第 4.2 节会结合长度和截断继续检查这一点。
参考答案的质量也会直接影响训练。 如果答案里多写了文章没有提到的关键词,训练程序仍会尝试提高这些词的概率。这就是为什么上一章既要检查分号,也要核对关键词含义,不能只确认 JSON 格式正确。
1.2 梯度与参数更新
现在,模型给正确分号的概率偏低,损失反映出了这个问题。但只拿到一个 Loss,程序还不知道内部那么多参数应该怎样改。
程序会根据损失算出各个可训练参数的调整线索,这些线索叫作梯度。再由优化器按规则执行更新,学习率控制步长。课程已经选好 AdamW 优化器,我们填写训练设置,计算和更新由程序完成。
Loss 描述当前预测的情况,梯度提供调整线索,优化器执行调整。
模型先从输入算出预测,这叫前向计算;得到损失后,沿着计算过程往回求梯度,这叫反向传播。求出梯度还不等于改好了权重,真正的更新由优化器执行。
把图书馆样本放回训练流程:
读图时,沿着“预测 → 损失 → 梯度 → 更新参数”看一遍,再看回到模型的虚线:下一批数据会使用更新后的参数重新计算预测。改变的是计算结果的产生方式,训练文件里的文章和答案仍保持原样。
进阶选读:梯度怎样提供调整线索,并手算一次更新
先只看其中一个参数:在其他参数暂时不变时,它稍微增大一点,损失倾向于增大还是减小,变化有多明显?损失对这个参数的变化率,给出了当前位置的调整线索。把各个可训练参数的这些变化率放在一起,就是 Gradient(梯度)。
梯度由训练框架通过反向传播计算,不需要我们逐个修改参数去试,也不是在训练页面手工填写的数。
有了梯度,接下来由 Optimizer(优化器)按更新规则真正改变参数,学习率(learning rate)控制更新的步长。先假设某个参数和它的梯度如下,用最简单的规则算一次:
某个可训练参数:0.30
当前梯度:0.20
学习率:0.10
按最简单的梯度下降规则:
新参数 = 原参数 - 学习率 × 梯度
= 0.30 - 0.10 × 0.20
= 0.28
这个参数的梯度是正数 0.20,表示在当前数值附近,增大它会使损失倾向于增大。因此,简单梯度下降往反方向调整,把参数从 0.30 减到 0.28;梯度为负时则相反。梯度描述的是当前位置附近的情况,步子迈得太大,仍可能越过合适位置,并不保证每步 Loss 都下降。
上面是简单梯度下降算例。课程训练使用 AdamW 优化器,还会参考之前梯度的统计信息,为各参数调整更新幅度。两种规则都需要先算出梯度,再执行参数更新。
回到图书馆任务,看到 Loss 偏高,下一步是检查数据与训练过程;训练结束后,再看新文章的关键词是否提取完整、是否按分号分隔。
实际训练通常把多条样本一起处理,还可能先累积几批梯度,再执行参数更新。下一节就来认识控制这个过程的设置。
2、训练轮次与批次
本节先掌握: 分清“一次处理几条”和“处理几批才更新一次”,算出课程为什么是每轮 50 步、3 轮 150 步。
上一节介绍了一次参数更新。现在有 1,600 条训练样本,我们需要决定:每次处理多少条、处理几批以后更新参数,以及整份数据训练几轮。
先看 LLaMA-Factory 的 Train(训练)页面。选好模型和数据后,下方就是训练参数。下面从真实界面中分别截出本节要认识的三项:批处理大小、梯度累积和训练轮数。截图只用于认识字段,完整配置在第 8.1 节汇总,第 31 章再实际填写。
这里只查看批次和轮数字段。训练时使用 keywords_train 和独立的 keywords_validation,验证集比例填 0;截图大图中的 keywords_small 和比例 0.01 不用于这套练习,完整填写方法见第 31 章。
本章后面的计算统一采用 Qwen/Qwen3-0.6B、单卡、1,600 条训练样本、每批 4 条、梯度累积 8、训练 3 轮。先认识下面三项,其余设置随用随讲:
| 界面名称 | 配置文件中的字段 | 本章取值与作用 |
|---|---|---|
| 批处理大小 | per_device_train_batch_size | 4:每张卡一次处理 4 条样本 |
| 梯度累积 | gradient_accumulation_steps | 8:累计处理 8 批后更新一次参数 |
| 训练轮数 | num_train_epochs | 3:整份训练数据训练 3 轮 |
这些设置也保存在 案例与源码-4-微调/configs/keywords_clean_train.yaml 中。YAML 是一种配置文件格式,每行用“参数名: 值”记录设置;例如 num_train_epochs: 3 就对应界面中的“训练轮数 3”。它保存训练安排,不保存训练样本。
2.1 批处理大小、训练轮数与更新步
先把界面设置与日志中的英文名称对应起来:
- Batch(批次):一次放进模型一起处理的样本组。这里一个 batch 是 4 条。
- Epoch(训练轮):训练集中的每条样本大致都被看过一遍,记为一轮。
- Step(更新步):训练程序完成一次参数更新,记为一步。没有使用梯度累积时,处理一个 batch 通常会完成一步更新。
在本课程按轮数训练的配置中,step 是程序算出的更新进度,无须再另填一个总步数。
1,600 恰好能被 4 整除:
1 个 epoch:1,600 ÷ 4 = 400 个小 batch
这 400 是处理的小批次数。课程还设置了梯度累积 8,因此不代表更新了 400 次。接下来算清楚:每处理 8 批才更新一次,会得到多少个更新步?
2.2 梯度累积与有效批次
梯度累积(gradient accumulation) 的意思是:处理完一个小 batch 后,先不更新参数,而是把本次得到的梯度暂存下来;连续处理若干个小 batch 后,再统一更新一次参数。
先按课程配置走一遍:用一张 GPU(通常简称“单卡”),第 1 批处理 4 条,暂不更新;第 2 批再处理 4 条,继续累积梯度……处理完第 8 批,才执行一次更新。然后开始下一组:
单卡 batch size = 4
梯度累积次数 = 8
4 条 × 8 次 = 32 条样本后更新一次参数
这 32 条叫作有效批次大小。在单卡场景下,可以先这样理解:
有效批次大小 = 单卡 batch size × 梯度累积次数
假设换了一批更长的文章,模型已经加载成功,但每次处理 4 条时显存不够了。 我们想改为每次处理 2 条,同时仍然每 32 条更新一次,应该一起改哪项设置?
先只改 batch:每批 2 条、仍然累积 8 批,就变成每 16 条更新一次。每次同时处理的样本少了,但更新也变得更频繁。
如果希望仍然每 32 条更新一次,就要把累积次数同步改为 16。batch 管一次处理多少条,梯度累积管处理几批才更新。 数据没有减半,变化的是分组方式:
| 单卡设置 | 一次处理 | 更新前处理几批 | 有效批次 |
|---|---|---|---|
| 原配置:batch 4、累积 8 | 4 条 | 8 批 | 32 条 |
| 只改 batch:2、累积 8 | 2 条 | 8 批 | 16 条 |
| 配套修改:batch 2、累积 16 | 2 条 | 16 批 | 32 条 |
图中对照的是表中的第一种和第三种安排。每个小框是一批,框内的数字是样本条数;沿箭头依次处理,并没有把 32 条一起放进 GPU。累计满 32 条以后才更新,下一组再使用新参数。
在样本长度等条件相同时,每次处理 2 条通常比处理 4 条少保存一些计算中间结果。降低这部分显存占用靠的是减小单卡 batch;增加累积次数,是为了维持每次更新所用的样本数。 如果 batch 仍为 4,只增加累积次数,当前这批 4 条所需的空间不会因此缩小。
调整时再看:有效批次相同,运行就一定相同吗
反过来,显存充足时,batch 8、累积 4 也能得到有效批次 32,但一次要同时处理更多样本。有效批次相同,不代表速度和训练结果完全相同,也不保证调整以后一定不会报错;第 5 节会解释其他显存占用。
再把一整轮算完:
训练样本:1,600 条
小批次数:1,600 ÷ 4 = 400 批
每轮更新:400 ÷ 8 = 50 步
训练 3 轮:50 × 3 = 150 步
这个计算以单卡、关闭序列打包、全部 1,600 条训练样本被保留为前提;“打包”是把多条短样本拼在一起处理,课程配置不启用。第 31 章的示例启动日志也对应 50 步/轮、共 150 步。样本数或打包方式改变后,应重新核对日志;验证集不参与这些参数更新。
进一步对照:更新频率、验证时机和真实资源变化
把三种安排都算一遍,就能看到修改的连带影响:
| 单卡设置 | 每轮更新步数 | 3 轮总步数 | 每 50 步验证一次,相当于 |
|---|---|---|---|
| batch 4、累积 8 | 1600 ÷ 32 = 50 | 150 | 每轮检查一次 |
| batch 2、累积 8 | 1600 ÷ 16 = 100 | 300 | 每半轮检查一次 |
| batch 2、累积 16 | 1600 ÷ 32 = 50 | 150 | 每轮检查一次 |
三种配置都把训练集练了 3 遍,但第二种更新得更频繁,按步数设置的验证间隔也落在了不同进度上。调整 batch 后,要一起核对有效批次、总步数和验证时机。 第 9 节会解释验证与保存的具体设置。
这样修改会有多大资源收益?第 33 章已有一组真实对照:同一张 V100、同一个模型与课程数据,两组各完成 50 步。batch 4/累积 8 的最高采样显存约为 27.92 GiB,整次命令耗时约 301 秒;batch 2/累积 16 则约为 21.71 GiB、560 秒。本次每批处理更少,留出了更多显存余量,也花了更多时间。
这组 50 步实验统计了从加载到保存的最高采样显存与整次耗时,用来比较资源开销;任务效果还需另行评估。完整记录与复现方法见第 33 章的 batch 对照。
读到这里可以自查: 每次处理 2 条,能不能每 32 条才更新一次?能,需要累积 16 批;“一批”与“一次更新”不必是同一件事。
2.3 epoch 不是越多越好
如果练习题越答越熟,新文章反而答得更差,还要继续增加轮数吗? epoch 增加,会让模型反复看到同一批训练样本。适当重复有助于学习任务规律,但是否继续,要看未参与训练的数据。
第 29 章解释了泛化与过拟合。用到 epoch 上,就是每多练一轮,还要检查没有参与训练的同类文章是否也有改善。
例如,其他批次设置保持课程取值,训练 1 轮对应 50 次更新,3 轮对应 150 次。多出的两轮提供了更多学习机会,也增加了计算;是否值得,要看新文章上的回答和验证损失。
本章第 9 节已有同一次训练在第 50、100、150 步保存的结果,可以先比较这些阶段,再决定是否需要新实验。若验证表现持续变差,应按第 9.3.1 节回查原因,不能只因训练 Loss 还在下降就继续加轮数。
3、学习率与调度
本节先掌握: 学习率控制更新步长,调大不等于学得更好。预热和衰减先看懂变化方向,首次训练沿用课程设置。
批次和轮次决定了“多久更新一次、总共更新多少次”。学习率则影响“每次更新走多大一步”。
3.1 学习率设置
把学习率调大,是否就能更快学好关键词抽取? 学习率控制每次更新的步长。步子太小,有限轮数内变化可能不足;步子太大,也可能使训练不稳定。它不是“学习速度”或“掌握知识量”的直接刻度。
训练页中的“学习率”对应配置字段 learning_rate。课程取值是 5e-5,在 YAML 中写作 learning_rate: 5.0e-5,两种写法表示同一个数:
5e-5 = 5 × 10⁻⁵ = 0.00005
这里的 e-5 是科学计数法的写法,用来简短地表示这样的小数。数值虽小,是否适合当前训练,仍要结合模型、数据和优化器判断。
| 学习率情况 | 可能发生什么 |
|---|---|
| 过大 | 参数更新过猛,Loss 可能剧烈波动,甚至越训越差 |
| 过小 | 参数变化缓慢,在有限轮数内可能还没有学到任务规律 |
| 合适 | 在当前设置下,训练较稳定,验证表现也有改善 |
把这个含义用到课程设置上:若从原模型重新开始训练,保持其他设置一致,将 5e-5 改为 1e-4,设定的学习率就从 0.00005 提高到 0.0001,变成原来的 2 倍。它会让优化器按更大的学习率更新;经过多步后,梯度和参数也会不同,不能推断损失下降或学习速度恰好翻倍。
调整时再看:用什么证据比较学习率
怎样判断是否值得改? 先确认样本、答案标签和 Adapter 加载都正确。如果在多个记录区间内,训练与验证损失改善都很慢,可以把较大学习率作为待比较的假设;如果训练反复出现大幅波动,可以比较较小学习率。某一条 Loss 上升只值得继续观察,还不足以决定改值。
比较时,先从日志确认实际学习率按新设置变化,再看相同训练进度下的损失趋势和同一批验证输入的回答。若数字降得更快,漏词或格式错误却更多,就不能只凭下降速度保留新配置。
进阶选读:只改学习率,手算一次更新幅度
回到第 1.2 节那个参数:原值为 0.30,梯度为 0.20。暂时保持二者不变,只换学习率,按同一条简单梯度下降规则计算:
| 学习率 | 本次减去的数值 | 更新后的参数 |
|---|---|---|
0.10 | 0.10 × 0.20 = 0.02 | 0.30 - 0.02 = 0.28 |
0.01 | 0.01 × 0.20 = 0.002 | 0.30 - 0.002 = 0.298 |
学习率缩小到十分之一,这个算例中的更新幅度也缩小到十分之一。它控制的是“朝这个方向走多大一步”,不是“这次学会了多少知识”。上表继续使用教学数字和简单规则;实际 AdamW 的更新还涉及梯度历史,不能直接套用表中结果。
3.2 学习率预热与衰减
如果学习率填的是一个固定数字,为什么日志中的数字还会变化?因为还配置了学习率调度器(scheduler),让学习率随训练进度变化。
界面中的“学习率调节器”就是选择调度规则的地方;展开“其它参数设置”还能找到预热步数。这三项配合使用:
| 界面名称 | 配置写法 | 作用 |
|---|---|---|
| 学习率 | learning_rate: 5.0e-5 | 本例预热结束时达到的学习率 |
| 学习率调节器 | lr_scheduler_type: cosine | 预热后按余弦曲线逐渐降低 |
| 预热步数 | warmup_steps: 5 | 开始的 5 个更新步逐步提高学习率 |
预热(warmup)是训练开始时,先使用较小学习率,再逐步升到设定值,用来缓和刚开始的参数更新。
这里用最初 5 个更新步做预热,包含在总共 150 步之内。预热阶段线性升高;预热结束后,学习率再按余弦曲线逐渐降低,这个后半程叫作衰减。
主图横轴按实际步数比例绘制,前 5 步因此很窄;下方单独放大预热区,方便看清起步过程。这是配置示意,实际日志中的数值会在第 9.1 节一起看。
为什么后面逐渐降低?参数已经经过许多次调整,继续用较大的步长可能在较好的位置附近来回波动。衰减让后期更新逐渐减小,但它按预先设置的进度执行,并不会自动判断模型已经掌握了哪些知识。
调整时再看:预热步数与总步数变化会影响什么
假设总步数仍为 150,只把预热从 5 步改成 15 步,模型会用更多更新步慢慢升到 5e-5,后面的衰减阶段则由 145 步缩为 135 步。这个变化可以直接算出;训练是否更稳定,要结合日志检查。预热拖得过长,也会让有限训练中的更多步骤处于较小学习率。
总步数本身也参与调度。第 2 节若只减小 batch,使总步数从 150 变成 300,即使仍填 cosine 和预热 5 步,衰减也会按新的总进度展开。因此,比较同一次训练的第 50 步和第 150 步,与分别新跑“总共 50 步”和“总共 150 步”,不是同一个实验。具体规则见 Transformers 学习率调度说明。
4、序列长度与截断
本节先掌握: 长度上限包含输入和参考答案。缩短之前检查会丢掉什么,提高上限也不一定增加实际训练内容。
训练页面中的“截断长度”对应 cutoff_len,用来限制一条训练序列最多有多少 token。第 29 章已经看过对话模板和分词结果,这个长度需要把输入和参考答案一起算进去:
用户文本 + 任务指令 + 模板标记 + 助手参考答案
↓
完整训练序列的 token
沿用图书馆样本,实际计入长度的内容包括:
[用户角色标记] 市图书馆周末开设儿童阅读课……请提取关键词……
[助手角色标记] 市图书馆;儿童阅读课;公众号预约 [结束标记]
└──────────────── 整段分词后的 token 总数 ────────────────┘
这是内容组成示意,角色标记的实际写法由模板决定。cutoff_len: 2048 表示这段完整序列的上限为 2,048 token,不是文章篇数、汉字数,也不是 Chat 页单独设置的“最大生成长度”。
4.1 长度上限的影响
长度首先影响模型能看到什么,其次才是显存和速度。假设图书馆原本提供了一篇很长的活动介绍,“读者可通过公众号预约”放在文章后半段。如果截断后这句话没了,参考答案里的“公众号预约”却仍然保留,会发生什么?
模型看到的文章不再有预约方式,训练程序却仍要求它预测出这个关键词。这样一来,一条本来有依据的答案,在截断后就可能失去依据。第 29 章检查的是原始样本是否合理,这里还要检查模型实际收到的样本是否完整。
用长度表示,假设一条训练样本处理前共有 2,300 token,而上限只有 2,048,就不能完整保留它。不同部分被截去,会产生不同影响:
| 被截去的内容 | 对关键词任务的影响 |
|---|---|
| 用户文章的一部分 | 答案可能提到了模型已经看不到的内容 |
| 参考答案的一部分 | 模型无法学习完整的关键词和结束方式 |
| 必要的任务说明或上下文 | 输入与答案可能不再构成清楚、完整的任务 |
工具会按自身预处理规则分配输入与答案的长度,不能一概认为只删除文章末尾。
进阶选读:填充与截断有什么区别,短样本也会补到上限吗
长度还涉及填充(padding):两条长短不同的样本放在同一批处理时,工具通常需要把它们整理成一样长。例如,一条有 120 个 token,另一条有 200 个 token;如果按本批最长样本补齐,短的那条就要增加 80 个填充位置。
工具会标记这些用于对齐的填充位置,让它们不作为预测目标计入损失;这里也会用到第 1.1.1 节的标签忽略规则。
读图时可以用两个问题区分:上半部分是“这一批长短不一,怎样排齐”,下半部分是“这一条太长,哪些内容还能保留”。图中没有规定工具具体截掉哪一段,实际结果需要按下一节的方法检查。
因此,cutoff_len: 2048 是长度上限,不等于每条短样本都补到 2,048。填充到哪里还取决于批次和工具设置。把上限提高到 8,192,也不能直接认定所有样本都会占用 8,192 个位置。
还要记住:2048 是上限,不表示每条短样本都会占满 2048 个位置。 实际占用还与批次及填充设置有关。
4.2 检查实际训练长度
先检查课程数据经过 LLaMA-Factory 处理后的长度。下面的结果对应 Qwen/Qwen3-0.6B、qwen3_nothink 模板、cutoff_len: 2048 和 train_on_prompt: false。这项检查只运行数据预处理,不需要加载模型权重或训练。
先看样本有没有被丢弃或截断:
| 数据文件 | 原始条数 → 处理后条数 | 最长完整序列 | 被截断的条数 |
|---|---|---|---|
keywords_train.jsonl | 1,600 → 1,600 | 957 token | 0 |
keywords_validation.jsonl | 200 → 200 | 604 token | 0 |
这里的长度包含角色标记、用户输入、助手答案和结束标记,不是文章字数。两份数据都低于 2,048 的上限,预处理前后条数一致,也没有因截断改变答案目标的样本。
现在可以根据实际长度判断怎样改上限。保持当前数据和模板不变,先作下面的配置推演:
| 截断长度设置 | 对当前数据能作出的判断 | 要检查什么 |
|---|---|---|
| 保持 2048 | 已有预处理证据表明全部样本完整保留 | 对照上述条数与长度 |
| 提高到 4096 | 当前最长只有 957,不会因此多读到文章内容 | 实际序列和填充是否变化,再观察资源 |
| 降到 512 | 至少已有最长样本无法完整保留 | 重新检查被截去的输入、答案及样本条数 |
长度设置首先要保证输入和答案完整。 上面的 4096 与 512 是按已有样本长度作出的推演;实际显存还受批次长度和填充方式影响。排查自己的数据时,可以展开下面的标签核验。
选读:实际样本的输入、答案标签与长度核验
再看 input_ids 和 labels 是否对应。 从这 1,800 条数据中,按完整序列长度选出最短、中间和最长的三条:
| 样本位置(文件行号从 1 开始) | 完整长度 | labels = -100 的位置数 | 参与损失计算的位置数 |
|---|---|---|---|
| 短:验证集第 133 行 | 39 | 28 | 11 |
| 中:训练集第 636 行 | 158 | 136 | 22 |
| 长:训练集第 631 行 | 957 | 938 | 19 |

图中短样本的答案是 高校学报;分编;期刊管理。它的前 28 个位置是用户输入及角色标记,labels 为 -100;从位置 28 开始(索引从 0 计数),标签保留答案的 token ID。末尾的 <|im_end|> 和换行也计入目标,所以参与损失计算的位置数不等于关键词字数。-100 只表示该位置不作为预测目标,不表示模型看不到这段输入。
核对标签时,既要检查解码后的输入、答案和角色边界,也要确认每条样本都有参与损失计算的答案位置。表中三条样本与源记录对应,全部 1,800 条数据也没有答案标签全为 -100 的情况。可在配套 examples/preprocessing/report.json 中查找 decoded_input、decoded_target 和标签数组;下面给出检查脚本的运行方法。
代码复现:上传并运行预处理检查脚本
先完成第 31 章的环境安装、模型下载和数据上传,不需要先启动训练。脚本只用 CPU 检查数据,不会替你下载模型。
在 AutoDL 的 JupyterLab 终端准备目录:
cd /root/autodl-tmp/LLaMA-Factory
source .venv/bin/activate
mkdir -p configs
在 JupyterLab 文件面板上传两份本机文件:
本机 案例与源码-4-微调/ 中的文件 | 上传到 AutoDL 的位置 |
|---|---|
audit_keywords_preprocessing.py | LLaMA-Factory/ 根目录 |
configs/keywords_clean_train.yaml | LLaMA-Factory/configs/ |
这里使用课程附带的 CLI 训练 YAML,不是 WebUI 的“保存训练参数”文件。若已上传同名文件,先核对内容;本脚本要求其中的 model_name_or_path 仍为 Qwen/Qwen3-0.6B,实际读取的本地模型位置由下面的 --model-dir 单独指定。
回到刚才的 AutoDL 终端,先检查文件,再运行脚本:
ls audit_keywords_preprocessing.py configs/keywords_clean_train.yaml
ls data/keywords-clean/keywords_train.jsonl data/keywords-clean/keywords_validation.jsonl
python audit_keywords_preprocessing.py \
--data-dir data/keywords-clean \
--config configs/keywords_clean_train.yaml \
--model-dir /root/.cache/modelscope/models/Qwen--Qwen3-0.6B/snapshots/master \
--output-dir /root/autodl-tmp/keywords-preprocessing-check
将 --model-dir 换成模型下载完成时返回的实际目录;--output-dir 必须是尚不存在的新目录,不要指向已有训练结果。--data-dir 是待检查的数据位置,--config 提供模板、长度等检查条件,并不会因为其中写了 do_train: true 就启动训练。
完成后,在文件面板打开输出目录中的 report.json,对照上面的条数、截断和标签统计。脚本报配置条件不符时,先核对所上传的 YAML;不要删掉检查条件强行运行。
换成自己的数据或调整模板后,需要重新运行上面的预处理检查,对照原文查看模型实际收到的文章与答案,确认两者仍然完整、对应。具体的解码、标签核验和脚本操作保留在展开框中。当前数据适合 2,048 的上限,不代表换一批文档后仍然合适。
5、训练显存
本节先掌握: 模型加载成功,训练仍可能放不下。分清权重与计算中间结果,知道减小 batch 主要减少哪一部分;精度按硬件支持选择。
显存(VRAM) 可以先理解为 GPU(训练时主要承担计算的显卡)用来摆放模型和计算中间结果的工作台。工作台放不下,训练就会报内存不足;工作台越大,通常能放下更大的模型、更长的样本或更大的 batch。
这也把前面的界面设置串了起来:“截断长度”影响一条样本有多长,“批处理大小”决定同时处理多少条,“计算类型”影响数值的表示方式。它们都会影响显存,所以不能只按模型文件大小选择显卡。
5.1 显存的主要占用
沿着第 1 节的顺序,看看训练用的这张“工作台”上要放什么:
- 加载模型时,先放入权重。 模型要使用这些数值计算,权重本身就占一部分空间。此时还没有处理训练样本。
- 处理一批样本时,产生计算草稿。 模型各层算出的中间结果叫作激活值。后面求梯度还会用到其中一些结果,所以算出预测后不能立刻把草稿全部丢掉。
- 根据损失往回求梯度。 程序沿着刚才的计算关系往回求梯度,这个过程叫作反向传播。梯度也需要存放;启用梯度累积时,处理下一小批之前还要保留已经累积的梯度。
- 更新参数时,还要保留更新所需的信息。 AdamW 会参考历史梯度统计,这些信息叫作优化器状态,供后续更新继续使用。
因此,“模型加载成功”和“一批训练能够跑完”是两件事。后面几步还需要空间;训练框架、临时工作空间等也有额外的运行开销。
这也解释了第 2 节的配置调整:同一个模型,batch 从 4 改为 2,并不是把模型权重缩小一半,而是同时处理的样本少了,需要保存的中间结果通常也更少。文章变长,则会增加这部分负担。
所以,权重大小主要看模型和数值表示;激活值还要看实际序列长度和单卡 batch。 第 5.3 节的梯度检查点,就是在“保存草稿”和“需要时重算”之间作取舍。

先比较两张工作台:右边的模型没有变大,增加的是训练需要的其他内容。 再看下方三种办法,分别减少哪一部分。图中的物体大小不表示实测占用比例。
遇到“显存不足”,先找到发生的阶段,再对照每种办法减少什么:
| 准备调整什么 | 主要影响工作台上的哪部分 | 如何判断方向是否合适 |
|---|---|---|
| 减小单卡 batch | 同时处理样本产生的中间结果 | 模型已加载,处理批次时空间不足 |
| 降低实际训练长度 | 每条样本产生的中间结果 | 先检查输入和答案是否仍然完整 |
| 使用 LoRA | 大量原权重对应的梯度、优化器状态 | 接受冻结原权重,只训练新增分支 |
| 量化基础权重 | 原模型权重本体 | 加载大模型时,权重已占去过多空间 |
例如,连模型都没加载完就报错,改 batch 无法缩小待加载的权重;已经完成加载,处理长文章时才报错,则应先检查实际长度和 batch。学习率改变参数更新的幅度,并不减少这些数值的数量,不能用它解决显存容量问题。
5.2 数值精度与权重大小
模型参数是数值,计算机保存一个数时,也需要选择表示方式。这里说的数值精度,不是模型回答的准确率,而是这些数能表示得多细。
可以借记录小数理解:把 0.12345 写成 0.12 更粗略,写成 0.1235 更细;另外,一个格式能否表示特别大或特别小的数,又涉及它的数值范围。这是帮助区分“精细程度”和“范围”的例子,FP16 并不是固定保留两位小数。
FP32、FP16、BF16 是几种保存和计算数值的格式。在训练页的“计算类型”中,课程选择 fp16,配置对应 fp16: true、bf16: false。先看它们占用的空间:
| 格式 | 一个数占多少位 | 折合多少字节 |
|---|---|---|
| FP32 | 32 bit | 4 字节 |
| FP16 | 16 bit | 2 字节 |
| BF16 | 16 bit | 2 字节 |
8 bit 等于 1 字节。FP16 和 BF16 虽然占的字节相同,数值范围和精度分配却不同,不是只换一个名字;本次 AutoDL V100 使用 FP16,不使用原生 BF16。具体硬件与安装检查放在第 31 章。
进阶选读:按字节计算权重大小,为什么整场显存不会同比例缩小
把 0.6B 粗略按 6 亿个参数计算,若每个权重用 2 字节保存:
600,000,000 × 2 = 1,200,000,000 字节 ≈ 1.2 GB
同样的 6 亿个数,若全部按 FP32 的 4 字节保存,权重本体就是约 2.4 GB。数值的数量没变,表示它们所用的空间变了。这里均按十进制 GB 粗算,只算权重本体,还没有加入中间结果、梯度和优化器状态等。
所以,从 FP32 改用 FP16 后,不能只据这道题断言整场训练显存减半。实际还有混合精度安排,部分状态仍会使用 FP32;是否稳定、硬件是否支持,也要通过第 31 章的实际运算检查确认。LoRA 的可训练参数比例同样不能直接用来换算整场训练显存。
“计算类型”也不同于模型区域的“量化等级”。本章使用 FP16 计算、不启用量化;第 7 节再解释量化会改变什么。
选读:选择 FP16 后,日志为什么还会出现 FP32
混合精度是让不同计算或状态使用不同精度,不是所有数值都统一变成 16 位。示例配置开启 FP16,但启动日志还记录了 Upcasting trainable params to float32,表示可训练参数被提升为 FP32。它们并不矛盾:计算设置、参数保存和优化器状态需要分开看。
5.3 梯度检查点与显存
如果第 5.1 节中用于反向传播的“草稿”占了太多显存,能不能少保存一些,需要时再算?
梯度检查点(gradient checkpointing,也称激活检查点)会少保存一部分中间结果,需要时再计算一次。可以把它理解为:不把所有草稿都留在桌面上,只保留必要位置,后面用到时重算。代价是增加计算,换取较低的显存占用。
不要与训练存档混淆:
| 名称 | 在这里做什么 |
|---|---|
| 梯度检查点 | 在一次训练计算中减少中间结果的保存,节省显存 |
训练检查点,如 checkpoint-50 | 把某个更新步的权重、状态等保存到磁盘,供后续加载或恢复 |
在其他设置相同的条件下,开启梯度检查点,相当于“少存草稿、多做重算”;关闭后则省去这部分重算,但要保存更多中间结果。如果原来刚好放得下,关闭后可能在处理批次时显存不足;开启后能否以可接受的时间完成,则要同时看资源和耗时。
本次启动日志已有 Gradient checkpointing enabled.,说明它已经开启。跟做时先核对启用状态;若要比较开关的影响,再记录完整运行中的占用与时间,不能拿“每 50 步保存文件”替代这个开关。具体排查方法留在第 33 章。
显存中的另一项大开销,是大量可训练参数对应的梯度和优化器状态。接下来介绍的 LoRA,主要从这一部分减少训练负担。
6、LoRA 高效微调
本节先掌握: 原权重继续参与计算,训练主要改变新增分支;得到的 Adapter 仍需原模型配合。第 6.2—6.6 节解释设置的用途,首轮沿用 rank 8、alpha 16、dropout 0、target all,先验证训练和实际回答。需要比较某个设置时,再设计一次有明确目的的实验,不要求一开始调优所有选项。
6.1 全参数微调与 LoRA
第 28 章区分了“用什么反馈教模型”与“更新哪些参数”。本节保持 SFT 的文章与参考答案不变,重点看第二个问题。
关键词抽取需要模型理解文章,也需要它遵守统一的输出要求。如果直接更新原模型中几乎全部参数,就是全参数微调。相应的大量梯度和优化器状态也要占用显存。
LoRA换了一种做法:原权重继续负责原来的计算,在选定的层旁边加一条可训练的分支,让它学习需要补上的调整量。两条路径一起参与计算,训练时只改新增分支中的少量参数。
这属于参数高效微调(PEFT):训练时只更新一部分参数,降低相关开销。本章只展开课程会使用的 LoRA 及其量化方式 QLoRA。
LoRA 新增的这一组权重叫作 Adapter(适配器)。它不是独立的完整模型,计算时仍需要原模型。这里的“基础模型”是指它依附的原模型 Qwen/Qwen3-0.6B,不等于必须选第 28 章介绍的 -Base 预训练版本。
| 训练方式 | 原模型权重 | 新增参数 | 主要保存的任务权重 |
|---|---|---|---|
| 全参数微调 | 参与更新 | 通常不需要 LoRA 分支 | 更新后的完整模型权重 |
| LoRA | 冻结,不更新 | 在选定层增加并训练小分支 | Adapter 增量权重 |

图中只放大模型中的一个计算位置。两条路都参与计算,只有小分支接受训练。 这里相加的是计算结果,还不是最终关键词;图中块的大小也不表示显存占比。
“冻结”不是锁住模型文件,也不是训练时不加载它。原权重仍参与计算,只是不为这些冻结参数计算、保存用于更新它们的梯度和优化器状态。反向传播仍需经过相关计算,才能求出可训练分支的梯度。
回到显存组成,少了大量参数的梯度和优化器状态,这部分开销就能减少;原权重和计算中间结果仍需保留。至于关键词提取是否改善,还要看训练后的验证结果。
冻结原权重,也不保证加载 LoRA 后所有旧任务都保持原样。 新分支会影响计算结果,模型的回答方式仍可能改变。除了检查关键词效果,还应检查自己需要保留的行为,做法见第 32 章的回归检查。
在训练页向下展开“LoRA 参数设置”,先找到上方的秩、缩放系数和随机丢弃三项:

下方的“LoRA 作用模块”在图中留空;课程配置在这里填 all,右侧“LoRA 附加模块”留空。第 6.5 节解释作用位置,第 31 章第 7.4 节展示完整填写结果。
| 界面名称 | 配置写法 | 先理解它控制什么 |
|---|---|---|
| LoRA 秩 | lora_rank: 8 | 新增分支中间有多宽,影响可训练参数量 |
| LoRA 缩放系数 | lora_alpha: 16 | 配合 rank,控制新增分支的增量缩放 |
| LoRA 随机丢弃 | lora_dropout: 0 | 训练时是否随机遮住部分分支输入;本章不开启 |
| LoRA 作用模块 | lora_target: all | 在当前模型中哪些可用的线性层上添加分支 |
先认识这四项的位置和用途,下面各节再分别解释。其他 LoRA 变体保持未启用,矩阵图和完整手算过程可以按需展开。
6.2 A、B 矩阵与计算过程
原权重不更新,输出仍能变化,因为新分支贡献了一个可学习的调整量。LoRA 把这条分支写成两块小的参数矩阵 A、B;矩阵就是按行列排列的数值表。
同一份输入在原路径中照常计算,同时先经过 A、再经过 B 得到调整量。两路结果相加后,继续传给后面的层。
刚加上分支,会不会立刻把原来的输出改乱? 常见默认初始化让 A 从随机数开始,B 全为零。输入经过 A 后,无论得到什么数,再经过全零的 B,分支贡献都是零;训练随后让这个增量逐渐发生变化。因此,初始分支可以先不改变原路径的结果。
不要把它改成“A、B 都全为零”:在这个两矩阵相乘的结构中,两者都为零会使常规梯度更新无法把分支学起来。默认初始化及其他可选方式可查 PEFT LoRA 文档。本课保持工具的默认初始化。
进阶选读:A、B 的矩阵图、向量与一次完整手算
原模型权重不更新,为什么它的输出还能改变?看清这个问题,就能理解 LoRA 为什么需要与原模型一起加载。
模型内部用一组组数值计算。一组按顺序排列的数叫作向量,按行列排列的数值表叫作矩阵。本节关注的线性层会用参数矩阵对输入向量做计算,得到另一组数值。LoRA 就是在选定的这些位置,增加用 A、B 表示的两块小矩阵。
下面只画其中一个线性层,不是整个模型。x 表示送进这一层的一组数,h 表示这一层算出的结果,还不是最终关键词。第一遍先沿箭头看“两条路径 → 相加”,图中的行列数留到第 6.3 节计算参数量时再看。
同一份输入走两条路:原权重照常计算,新增的 A、B 分支计算一个调整量,两路输出相加后传给下一层。训练时只更新 A、B,原权重保持不变。这就是图中“冻结”与“可训练”的区别。
先不算矩阵乘法,只看一个两维教学例子的结果。假设同一输入经过原路径,输出为 [1, 2];经过 A、B,得到的调整量为 [0.1, 0.2]。暂时不加缩放系数,对应位置相加,就得到 [1.1, 2.2],再交给后面的层处理。
这就是“原权重不改,计算结果仍能改变”的原因。这里的数值是为了说明两路相加,不是实际模型权重;这些中间数也还不是关键词。想知道调整量怎样算出来,可以展开下面的手算。
选读:文字怎样变成参与计算的向量
第 29 章把文字变成了 token 编号。例如,“市”的编号是 22697,但这个编号只是用来找到对应的 token,不表示它的含义就是数值二万多。
模型会先按编号取出一组数值,作为这个 token 的初始表示,这一步叫作 Embedding(嵌入)。这组向量会继续经过模型各层计算,与上下文结合,而不是直接拿 token 编号当作词义大小做加减。编号与向量的对应关系可对照 PyTorch Embedding 说明。
选读:A、B 怎样相乘,并用两维数值手算一次输出
沿用图中“先经过 A,再经过 B”的顺序,先暂时不考虑缩放:
权重增量:ΔW = B × A
等效新权重:W = W₀ + ΔW
原路径的输出:W₀ × x
新增路径的输出:B × (A × x)
这个线性层的输出:h = W₀ × x + B × (A × x)
W₀ 是原权重,ΔW 读作“权重的改变量”。权重与权重相加,输出与输出相加,不能把 W₀ × x 的计算结果直接与 B × A 这个矩阵相加。两条路径都要处理同一个 x。
训练期间保留 W₀,更新 A、B;多个样本共同影响这些小矩阵中的数值。这是 LoRA 的基本结构。
用小数字算一遍。 为了手算,把图中的 1000 维缩成 2 维,并假设下面是一组训练后的数值;暂时仍不加缩放系数:
输入 x = [1, 2]ᵀ (ᵀ 表示把这两个数竖着排列)
原权重 W₀ = [1 0] A = [0.1 0.2] B = [0.2]
[0 1] [0.4]
原路径保持输入不变;新增路径先把两个数算成一个数,再变回两个数:
原路径:W₀ × x = [1, 2]ᵀ
先经过 A:0.1 × 1 + 0.2 × 2 = 0.5
再经过 B:[0.2 × 0.5, 0.4 × 0.5]ᵀ = [0.1, 0.2]ᵀ
两路相加:h = [1, 2]ᵀ + [0.1, 0.2]ᵀ = [1.1, 2.2]ᵀ
这两个数还不是最终关键词,而是继续传给后面层的中间输出。此例只用来说明“两条路径处理同一个输入,再把结果相加”;尺寸太小,不用它计算节省比例。下一节再换回大矩阵,计算参数量。
6.3 rank 与可训练参数量
rank(秩,简称 r)控制新增分支中间有多宽。 课程设置 lora_rank: 8,表示输入经过 A 后,先形成 8 个中间数,再由 B 继续计算。
从 8 改成 16,分支会增加可调整的数值,表达调整量的空间更大,相关训练开销也会增加。更多可训练参数不保证关键词提取更好。 首轮先沿用 8,需要比较分支容量时,再看下面的数量计算,并同时核对下一节的 alpha。
进阶选读:rank 8 与 16 的参数量、实际日志和比较方法
把第 6.2 节的分支画宽一点,具体会多出多少参数?假设某个线性层的输入和输出都包含 1000 个数,A 先把 1000 个数变成 r 个,B 再变回 1000 个:
| rank | A 的参数量 | B 的参数量 | 新增可训练参数合计 |
|---|---|---|---|
| 8 | 8 × 1000 = 8,000 | 1000 × 8 = 8,000 | 16,000 |
| 16 | 16 × 1000 = 16,000 | 1000 × 16 = 16,000 | 32,000 |
从 8 改成 16,这个分支有了更多可调整的数值,能表达的修正范围更大;新增权重及其梯度、优化器状态也更多。 原路径的 100 万个权重仍在。上表是同一个教学层的尺寸对照,不能推导出整场训练显存翻倍。
什么时候值得比较 rank?如果输入完整、标注一致、Adapter 已正确加载,而现有训练在训练集和验证集上都持续表现不足,可以将分支容量作为一个待验证因素。如果训练题已经答得很好,验证题却变差,就应先检查第 9 节的过拟合问题。
比较时,从相同基础模型分别新建 Adapter,并在日志中核对可训练参数量,再比较同一份验证数据上的损失、关键词回答与资源开销。调整时还要考虑下一节的 alpha,二者会共同决定增量缩放。
沿用表中的 rank 8,计算节省了多少可训练参数。 A 的尺寸为 8 × 1000,B 为 1000 × 8。矩阵相乘后,B × A 仍是 1000 × 1000,能作为与原权重形状相同的增量。
全参数更新这一个矩阵:1,000,000 个可训练参数
LoRA 更新 A 和 B:8,000 + 8,000 = 16,000 个可训练参数
16,000 ÷ 1,000,000 = 1.6%
1,000,000 ÷ 16,000 = 62.5
减少的是这个层的可训练参数,不是把原来的 100 万个权重删除了。此时原权重与 A、B 一共仍有 1,016,000 个参数,只是其中 16,000 个需要更新。不能由此说“整场训练显存缩小 62.5 倍”或“速度提升 62.5 倍”。
这里的“秩”与原矩阵有什么关系?
B × A 的秩不超过 r,不保证恰好等于 r。这个小宽度限制了增量能表达的变化,并不要求原矩阵本身的秩就是 8。
实际模型更新了多少参数? 本次清洗版训练使用 r=8,启动日志记录:
trainable params: 5,046,272
all params: 601,096,192
trainable%: 0.8395
也就是约 505 万个参数参与更新,占日志统计全部参数的约 0.8395%;分母包含基础模型与新增 Adapter。
教学层的 1.6% 与整模型的 0.8395% 不矛盾:真实模型有多层、不同矩阵尺寸和不同的作用位置。完整启动记录见第 31 章的训练日志 案例与源码-4-微调/results/keywords-clean/training/train.log。
6.4 alpha 与增量缩放
alpha 控制新增分支的结果怎样缩放后相加。 在本课程的标准 LoRA 中,缩放倍数是 alpha/r。课程 rank=8、alpha=16,所以分支结果先乘以 2,再与原路径结果相加。
先分清两个环节:学习率控制训练时权重怎样更新,alpha/r 控制计算时分支结果怎样加入。 alpha 不直接改变矩阵尺寸,也不能当作学习率的替代项。
首次保持 rank=8、alpha=16。以后改 rank 时,要连同这个比值一起看:只把 rank 改为 16,alpha 仍为 16,比值就从 2 变成 1。需要做配套对照时,再展开下面的例子。
进阶选读:alpha 的数值例子、与 rank 的配套变化及公式
沿用第 6.2 节的教学数字,暂时固定输入与 A、B:原路径输出 [1, 2],新增分支算出 [0.1, 0.2]。当 alpha/r 为 1,两路相加得到 [1.1, 2.2];保持 rank 不变,将 alpha 加倍,使系数变为 2,分支先乘 2,再相加得到 [1.2, 2.4]。矩阵尺寸没有变,改变的是同一份分支结果加入时的大小。
这也解释了为什么修改 rank 时需要一起看 alpha:
| 设置 | rank | alpha | alpha/r | 发生了什么 |
|---|---|---|---|---|
| 课程起点 | 8 | 16 | 2 | 当前分支宽度与缩放 |
| 只增加 rank | 16 | 16 | 1 | 分支变宽,缩放系数同时减半 |
| 增加 rank,并维持系数 | 16 | 32 | 2 | 分支变宽,缩放系数仍为 2 |
如果实验要比较“分支宽度变化、相加前的缩放倍数保持一致”,第三行就是一种配套安排。它仍不保证两组梯度、训练过程或输出相同。第二行也可以作为实验,只是结论要承认它同时改变了容量和缩放,不能全部归因于 rank。
本节使用标准 LoRA 的 alpha/r 规则,其他变体可能不同。本次 PEFT 0.18.1 的缩放实现
分支参与计算的方式变化,也会影响随后求出的梯度。比较 alpha 时,应先固定 rank 与作用位置,核对配置中的系数,再比较验证表现。
选读:把 alpha/r 放回公式中计算
权重增量:ΔW = (alpha / r) × B × A
线性层输出:h = W₀ × x + (alpha / r) × B × (A × x)
暂时固定 A、B,假设 B × A 中某个位置是 0.01,原权重对应位置是 0.30:系数为 1 时,等效权重是 0.31;系数为 2 时,是 0.32。这是前向计算中如何组合权重的例子,不是说优化器每次更新必然翻倍。
6.5 target modules 与作用位置
界面中的“LoRA 作用模块”对应 target modules(目标模块),指定把 LoRA 分支加到哪些层上。课程使用 lora_target: all,由工具为当前模型识别可应用 LoRA 的线性层。它与旁边的“附加模块”不是一回事;本课程“附加模块”留空。
模型中同一种层名可能重复出现,每个匹配的层各有自己的 A、B;原权重继续保持冻结。
调整时再看:缩小作用范围会怎样,实际添加到了哪些层
假设保持 rank 为 8,把作用位置从当前的 all 缩小到只选 q_proj、v_proj:模型中匹配这两个名称的层仍添加分支,原先其他位置的分支则不再添加。因此,可训练参数和相关状态会减少,但模型也少了一些可以学习调整的位置。rank 决定每条分支有多宽,target 决定哪些地方有分支。
这类比较适合回答“缩小作用范围,能否在减少开销时保留任务效果”。先从启动日志核对可训练参数量,并查看 adapter_config.json 的 target_modules,确认实际作用位置;再比较同一份验证数据上的回答。
这次 Qwen3 的哪些层加入了 LoRA:
清洗版 Adapter 配置记录了以下名称:
| 层名 | 所属计算部分 | 这里需要理解什么 |
|---|---|---|
q_proj、k_proj、v_proj、o_proj | 注意力部分的线性映射 | 这些位置会添加并训练 LoRA 分支 |
gate_proj、up_proj、down_proj | 前馈网络,即每层中继续变换数值的部分 | 这些位置同样参与本次 LoRA 训练 |
这些名称在不同网络层中重复出现,各自可以有对应的 A、B。更换模型时,应重新检查它的层名与实际作用位置。
6.6 LoRA dropout
界面中的“LoRA 随机丢弃”对应 LoRA dropout,是在训练时对 LoRA 分支的部分输入值进行随机丢弃:本次暂时遮住部分输入,下次重新随机选择。
第 6.1 节截图中的辅助文字写成了“LoRA 权重随机丢弃的概率”,容易让人误以为会删除权重。这里按本次 PEFT 的实际计算理解为对分支输入应用 dropout,A、B 权重仍然保留。
课程设置为 lora_dropout: 0,即不启用。假设改为 0.1,训练时分支输入中的每个数值会以 10% 的概率被暂时置零,A、B 的尺寸和参数量保持不变。
调整时再看:什么时候比较 dropout,怎样检查效果
这样做是希望分支少依赖某些输入值的固定组合,有时有助于缓解过拟合,也可能让有限训练中的学习更困难。如果核对数据和预处理后,仍出现训练表现持续改善、验证表现持续变差,可以把 dropout 作为候选调整之一。它不负责修正错误标注,也不是省显存开关。
若进行对照,两组从相同基础模型开始,保持其他训练条件一致;重点看验证损失和实际回答是否改善,即使训练损失稍高,也要结合验证结果判断。
正常推理时 dropout 会关闭。首次训练保持课程的 0,遇到过拟合问题再考虑是否需要比较其他值。
现在再读本节开头的四项配置:target 决定分支加在哪里,rank 决定分支有多宽,alpha/r 决定结果怎样缩放后相加,dropout 决定训练时是否随机遮住部分分支输入。
7、QLoRA 量化微调
本节先认识用途: QLoRA 进一步节省基础权重的存储,但仍有其他训练开销。本课程首轮不启用量化,编码细节属于进阶选读。
本课程先保持“微调方法”为 lora、“量化等级”为 none,使用 Qwen3-0.6B 练习普通 FP16 LoRA。更换较大的模型后,如果基础权重占用过多显存,再考虑下面的 QLoRA;不要仅因页面提供量化选项就全部开启。
QLoRA 可以先理解为“量化版的 LoRA”。它仍然训练 LoRA Adapter;不同之处在于,训练时加载的基础模型会以更低位数保存,从而减少基础模型权重的显存占用。
这里的量化(quantization),是用更少的比特近似保存模型参数。例如原来一个权重用 16 bit 保存,量化后用 4 bit 编码表示;省下了空间,但数值可能出现近似误差,任务效果仍需验证。
选读图解:全参数微调、LoRA 与 QLoRA 的完整对照
7.1 量化与近似误差
量化可以理解为:准备一组有限的代表值,把每个原数替换为接近的代表值,并保存对应编号。4 bit 能表示 16 个编号,因此不能精确保留任意小数。计算时根据编号还原近似值,这一步叫作反量化。
先只跟踪一个数:在下面的等间距量化教学例子中,原来的 0.27 经过“选一个接近的代表值 → 保存编号和缩放因子 → 还原”,得到的是 0.304,相差 0.034。
为什么还原后不再是 0.27?因为保存的是有限选项中的编号,没有保留这个数的全部信息。量化用近似换空间,反量化取回的是近似值。 具体编号怎样算、QLoRA 使用的代表值有什么不同,见下方选读。
选读:一个权重怎样变成 4-bit 编号,又怎样还原
将上面的教学例子展开,假设一组参数是:
0.27,0.58,-0.75,1.52
这一组绝对值最大的数是 1.52。我们把它保留下来作为缩放因子,再把各数除以它,将数值缩到 -1~1 的范围。这里只跟踪第一个数 0.27:
| 步骤 | 计算或保存的内容 | 这一步做了什么 |
|---|---|---|
| 缩放到统一范围 | 0.27 ÷ 1.52 ≈ 0.1776 | 先变成便于查表的小数 |
| 找最近的代表值 | 0.1776 → 0.2 | 在 -1~1 的 16 个等间距代表值中选最近的一个 |
| 保存编号 | 0.2 对应编号 9,二进制为 1001 | 编号只需 4 bit;同时保留这组的缩放因子 |
| 反量化 | 0.2 × 1.52 = 0.304 | 用编号查回代表值,再恢复到原来的量级 |
16 个代表值按从小到大编号为 0~15,编号 9 对应 -1 + 9 × 2/15 = 0.2。编号保存的是“选了哪个代表值”,并没有另外记住原数 0.1776。
所以,还原后是 0.304,不是原来的 0.27,绝对误差为 0.034。反量化是恢复近似值,不是无损解压。 模型是否还能完成任务,要继续评估,不能仅凭文件变小就判断效果。
这里使用的是等间距量化示例。QLoRA 中常用的 NF4 不是这张等间距表,而是针对权重分布设计的一组非等间距代表值;这里只借简单数字理解“存编号、存缩放因子、存在近似误差”。QLoRA 论文
拓展计算:为什么 4-bit 的实际存储还包含额外开销
除了参数的编号,还要保存把这一组数恢复到原量级的缩放因子等辅助信息。假设四个数为一组,原来都用 FP16 保存,一共是 4×16=64 bit。现在四个 4-bit 编号占 16 bit;若这一组的缩放因子用 FP32 保存,还要加 32 bit,合计 48 bit。即使暂时不计其他开销,这个小例子的总量也不是原来的四分之一。
四个数分一组只是为了方便计算。若改成每 64 个数共用一个 FP32 缩放因子,编号与缩放因子共占 64×4+32=288 bit,相当于每个数平均 4.5 bit。这样就能看出:缩放因子也占空间,只是被一组数分摊了。这里仍未计入码表等其他开销,也没有加入下一节的双重量化。
7.2 QLoRA 的显存开销
回到第 5 节的显存组成:LoRA 已经减少了需要更新的参数及相关状态,QLoRA 进一步压缩的是仍需加载的基础模型权重。Adapter 的梯度、优化器状态和中间计算结果仍然需要空间。
训练时,量化基础权重保持冻结,需要计算时按实现恢复到相应计算精度参与运算;可训练的 LoRA 参数并不是简单地全变成 4-bit。因此不能只看“模型已经量化”就认定一定能训。序列长度、单卡 batch、训练工具的实现和实际 GPU,仍会决定能否稳定运行。
还要区分报错发生在哪一步:基础权重尚未加载完就显存不足,先检查模型大小、加载精度和 GPU 上其他进程;权重已加载、开始处理批次后才不足,再优先检查长度和单卡 batch。减小 batch 不会缩小基础权重本体,详细排查放在第 33 章。
拓展术语:双重量化与分页优化器
QLoRA 还采用了两项优化:双重量化(double quantization) 进一步量化缩放因子等辅助常数,减少这些额外信息的存储;分页优化器(paged optimizer) 通过在 CPU 与 GPU 之间按需迁移优化器状态,缓解训练中的显存峰值,但可能增加传输等待。它们都不改变“冻结量化基础权重、训练 LoRA 参数”的更新范围,也不保证任何配置都能放进显存。QLoRA 论文
7.3 LoRA 与 QLoRA 的选择
| 当前情况 | 优先考虑什么 |
|---|---|
| 本课程 Qwen3-0.6B 能在现有显卡上完成 LoRA | 保持 LoRA,先检查训练与任务效果 |
| 换大模型后,仅加载基础权重就占去过多显存 | 检查加载精度,再评估 QLoRA |
| 能加载模型,但处理长样本或大批次时显存不足 | 先检查长度与 batch,不能只靠量化解决 |
| 需要验证两种方法的资源差异 | 固定模型、数据和其余配置,分别记录实际资源与效果 |
调整时再看:从 LoRA 改为 QLoRA,要比较哪些结果
把当前配置中的量化等级从 none 改为适当配置的 4-bit 加载,主要变化是基础权重的保存与计算方式;学习的仍然是 LoRA 分支。额外的量化、反量化计算和具体实现会影响速度,因此 QLoRA 也可能更慢。它带来的近似误差是否影响关键词选择,还要检查回答。
对本课程已能完成普通 LoRA 的 0.6B 模型,先用现有配置完成训练与效果检查。将来换大模型,若权重占用成为主要限制,再比较 QLoRA:核对量化是否实际生效,记录能否完整运行、显存与耗时,最后比较同一份验证数据的损失和回答。能容纳模型、运行成本合适、任务效果可接受,是三个需要分别检查的结果。
量化依赖、配置和资源对比留到第 33 章第 2.5、2.6 节选做,结果整理方法见该章第 5.5 节。
8、训练配置与显存估算
本节先掌握: 对照第 8.1 节,把配置读成一次完整的训练安排。第 8.2 节的计算器是更换配置时的选读工具,估算还需要实机验证。
现在回到本章开头的任务:使用第 29 章清洗并划分的数据,在 AutoDL 的现有 V100-32GB 单卡上,对 Qwen/Qwen3-0.6B 做 LoRA 微调。下面不增加新参数,而是试着把整份配置读成一次训练安排。
8.1 首轮训练配置
第 2 节先认识了训练页面和三个批次参数。现在回到 案例与源码-4-微调/configs/keywords_clean_train.yaml,把整套设置连起来读:
先用本章开头的五项设置,把安排说清:每批 4 条、累积 8 批后更新,1,600 条训练数据练 3 轮;学习率按 5e-5 调度,每条完整训练序列最多保留 2048 个 token。预计每轮更新 50 步,合计 150 步。
再核对两个条件:使用匹配的模型与模板,V100 上采用已验证的 FP16。LoRA 先沿用课程设置;日志、验证与保存间隔属于观察和留存安排。
运行时查阅:首轮训练的完整字段与取值
| 中文名称与配置字段 | 本次取值 | 对应前面学到的含义 |
|---|---|---|
模型路径model_name_or_path | Qwen/Qwen3-0.6B | 指定要微调的模型 |
对话模板template | qwen3_nothink | 按模型要求组织输入与答案 |
思考模式enable_thinking | false | 请求关闭思考;实际处理取决于模板,见第 32 章对照 |
训练集dataset | keywords_train | 1,600 条样本,用于更新参数 |
验证集eval_dataset | keywords_validation | 200 条样本,用于训练中检查 |
自动验证划分val_size | 0 | 已有独立验证文件,不再额外划分 |
截断长度cutoff_len | 2048 | 每条训练序列的 token 数上限 |
单卡批处理大小per_device_train_batch_size | 4 | 每张卡每个小批次处理 4 条样本 |
梯度累积步数gradient_accumulation_steps | 8 | 累积 8 个小批次后更新一次参数 |
训练轮数num_train_epochs | 3 | 训练集学习 3 遍,预计共 150 个更新步 |
优化器optim | adamw_torch | 使用 PyTorch 的 AdamW 更新参数 |
学习率learning_rate | 5.0e-5 | 本例预热结束时达到的学习率 |
学习率调度器lr_scheduler_type | cosine | 预热后按余弦曲线降低学习率 |
预热步数warmup_steps | 5 | 前 5 个更新步逐步提高学习率 |
FP16 混合精度fp16 | true | 开启本次 V100 使用的混合精度训练 |
BF16 混合精度bf16 | false | 本次不开启 BF16 |
LoRA 秩lora_rank | 8 | 设置新增分支的中间宽度 |
LoRA 缩放系数lora_alpha | 16 | 配合 rank,按 alpha/r 缩放分支结果 |
LoRA 随机丢弃lora_dropout | 0 | 不随机丢弃分支输入 |
LoRA 作用模块lora_target | all | 在工具识别的适用线性层添加分支 |
日志间隔logging_steps | 5 | 每 5 个更新步记录一次日志 |
验证间隔eval_steps | 50 | 每 50 个更新步检查一次验证损失 |
保存间隔save_steps | 50 | 每 50 个更新步保存一次检查点 |
200 条测试数据留给最终检查,不参与本轮训练或检查点选择。第 31 章会把这份配置对应到 WebUI 的填写位置,并提供可选的命令行入口。
这份配置的取值依据需要分开理解:模型与模板要匹配,计算精度要适合硬件,长度要能保留完整样本,这些都有明确的检查条件;batch、学习率、轮数和 LoRA 参数则组成了一套已运行过的起始安排。已有记录便于复现和比较,不能据此称它为最优组合。
选读图解:整套训练配置与产物的对应关系

先不翻前文,试着向别人说明这次训练:
- 训练、验证、测试各用多少条?哪一份数据会让参数发生更新?
- 每次只处理 4 条,为什么不是每轮更新 400 次?第 50 步时准备做什么?
- 使用 LoRA 后,训练结束得到什么?拿到这个文件,是否就能证明关键词抽取变好了?
参考回答:把配置连成一段完整的训练说明
我们用 1,600 条文章与参考答案训练,只更新选定层的 LoRA 分支。每批处理 4 条,累计 8 批、也就是 32 条后更新一次;每轮 50 步,3 轮共 150 步。每 5 步记录日志,每 50 步用 200 条验证数据检查并保存检查点。验证过程不更新参数,但其结果会用于选择检查点;200 条测试数据继续保留。
训练得到 Adapter、日志和检查点。Adapter 需要与对应的原模型一起使用;接下来还要比较实际生成的关键词,不能只凭 Loss 降了或文件保存成功,就宣布任务效果改善。
能说明这段安排以后,再做一道配置判断题:假设模型加载成功,但处理批次时显存不足。同学建议同时把 batch 改成 2、学习率改成 1e-4、rank 改成 16、截断长度改成 512。你会怎样处理? 先说出报错对应哪部分占用,再决定保留哪些修改。
参考判断:每个修改都要有目的和检查依据
先把 batch 改为 2,配合累积 16,维持有效批次 32;学习率和 rank 保持课程取值。这次要解决的是同时处理样本的空间问题,增大学习率不会减少占用,增大 rank 还会增加可训练参数的相关开销。
长度先保持 2048。已有记录中最长样本为 957,直接降到 512 会使部分样本无法完整保留。如果后续确实需要缩短,应先检查截断后的输入和答案,不能为了通过第一批而丢掉关键词的来源。
然后核对启动日志:有效批次仍为 32,总更新步数仍为 150。观察原来的报错位置是否通过,并继续看更长批次及验证过程,记录显存与耗时;若仍报错,再按第 33 章检查其他占用。资源问题解决后,实际回答仍按第 32 章验证。
修改配置前,可以用一句话记录实验目的:“我准备改什么,希望改变哪一步,之后用什么结果判断。”例如,比较学习率就固定数据、模型、模板和其他训练设置;比较 rank 并维持相加前的缩放倍数,则按第 6.4 节配套处理 alpha。一次比较围绕一个明确问题,并记录相关设置的变化。 不同实验从约定的相同起点开始、使用不同输出目录,才能把结果对上。
8.2 使用显存计算器
本小节选读。 首次跟做已有课程配置,可以先继续第 9 节。以后更换模型或训练设置时,再用计算器作粗略估算,之后通过实机试跑检查。
记住一个边界:计算器的估算值不等于真实峰值,也不能证明显卡精度兼容或训练一定能完成。
选读操作:填写显存计算器、理解结果并与实际运行对照
打开 ApX 显存计算器,选择“微调”和“LoRA”,再填写模型与主要训练参数。不要使用“推理”页面估算训练,因为它不包含同样的训练状态。
对照上表,填写模型 Qwen3-0.6B、rank 8、batch 4、长度 2048、梯度累积 8:

图中 batch 的提示文字提到了“每次优化步”,读时要分清:输入框里的 4 是单卡每个小批次的样本数,配合累积 8 次,才得到每次参数更新的有效批次 32。
计算器中还有三个需要注意的地方:
- 精度统一显示为 FP16/BF16;本次 V100 配置使用 FP16,不能凭这个合并选项判断硬件兼容性。
- 没有 V100 预设时,选择单卡并自定义 32 GB 显存容量;这只匹配容量,不能据此估算 V100 的训练速度。
- 优化器选 AdamW,梯度检查点开启、页面比率为 100%。这是第 5.3 节说的减少中间结果保存,不是训练文件的保存频率。
同一组设置的结果约为 9.8 GB:

这个数是按页面假设分项相加得到的预算:模型权重约 1.2 GB,中间结果约 7.1 GB,LoRA 优化器与梯度约 0.01 GB,框架开销约 1.5 GB,合计约 9.8 GB。各项是计算器估算值,不是训练过程中的测量值。
为什么第 33 章实际采样达到约 27.92 GiB? 两处记录的范围不同:
| 对照项 | 计算器的 9.8 GB | batch 4 对照实验的 27.92 GiB |
|---|---|---|
| 怎样得到 | 按长度 2048 等输入与页面公式估算各项预算 | 运行 LLaMA-Factory,每 200 毫秒采样一次整张 GPU 的已用显存 |
| 包含哪些条件 | 使用页面默认填充、运行开销等假设;没有 alpha、target 的独立入口 | 包含模型加载、训练、验证和保存期间的实际分配与缓存 |
| 数值表示什么 | 简化预算的合计 | 全程最高采样值 28,588 MiB,约 27.92 GiB,即约 29.98 GB |
实际软件在不同阶段会申请临时空间,并保留部分缓存;计算器的固定预算无法完整反映这个过程。现有记录没有逐项显存追踪,因此还不能把差额归到某一个组件。选配置时用估算筛选方案,确定能否稳定运行则要看第 33 章的实际资源记录。
读完这组截图,可以用前面的知识检查自己的理解:如果模型和精度不变,只把 batch 从 4 改为 8,模型权重不会随之翻倍,主要增加的是并行处理样本的计算占用;如果只提高长度上限,影响还取决于实际样本长度、填充方式以及计算器的假设。
需要估算新配置时,可以先只改 batch,记录新读数;再恢复原值,比较长度上限。假如计算器随上限提高而给出更高估算,先看它是否按所填长度计算,不要直接套到当前最长 957 token 的实际数据上。估算帮助筛选待试的方案,真正保留哪个设置,还要对照实际运行的占用、耗时和样本完整性。
估算示例:同一个 8B 模型为什么推理与全参数训练占用不同
下面比较同一计算器在两种输入条件下的估算,用于理解计算过程的差别:
| 模式 | 估算输入条件 | 页面估算 |
|---|---|---|
| 推理 | Qwen3-8B、FP16 权重、FP16/BF16 KV 缓存、batch 1、长度 1024、并发 1 | 17.2 GB |
| 全参数微调 | Qwen3-8B、FP16/BF16、batch 1、长度 1024、梯度累积 1 | 143.4 GB |
全参数训练还要保存大量梯度和优化器状态,所以估算明显增大。143.4 GB 不是 8B LoRA 的显存需求,也不能把两项的比值当成所有模型的固定规律。
9、训练日志与检查点
本节先掌握: 看训练进行到哪里、验证结果是否改善、产物保存在哪里。训练 Loss 下降后,还要检查实际生成的关键词。
配置决定预期,日志告诉我们实际发生了什么。下面使用 keywords-clean 数据的一次 150 步训练作为示例:本章学习读日志,第 31 章学习怎样运行,第 32 章再检查对应 Adapter 的回答。
先按时间顺序看三个位置:第 5 步出现一条训练日志,第 50 步完成第一轮并验证、保存,第 150 步完成三轮,再按验证结果选择检查点。 下面的日志、曲线和文件列表,就按这条顺序来读。
9.1 阅读一条训练日志
训练启动后,界面日志区或终端会先列出训练安排。下面摘取示例日志中的关键字段,省略时间和日志前缀:
Num examples = 1,600
Num Epochs = 3
Instantaneous batch size per device = 4
Total train batch size (w. parallel, distributed & accumulation) = 32
Gradient Accumulation steps = 8
Total optimization steps = 150
先找到 Num examples = 1,600、Num Epochs = 3 和 Total optimization steps = 150,就能与第 2 节的计算对应起来。有效批次对应 Total train batch size = 32;进度条中的 21/150 则表示已经完成 21 个更新步。
在示例的 trainer_state.json 中,第 5 步包含以下信息。这里只摘取四个字段,Loss 保留四位小数:
{
"step": 5,
"epoch": 0.1,
"loss": 4.1185,
"learning_rate": 0.00004
}
| 字段 | 这条记录表示什么 |
|---|---|
step: 5 | 已完成 5 个参数更新步 |
epoch: 0.1 | 第一轮约完成 10%;本轮每轮 50 步,5 ÷ 50 = 0.1 |
loss: 4.1185 | 最近记录区间内汇总的训练损失 |
learning_rate: 0.00004 | 第 5 次更新使用的学习率,即 4e-5 |
本次 logging_steps: 5,表示每 5 个更新步记录一次。结合梯度累积 8,可以把它读成:正常情况下处理 40 个小 batch 后,出现下一条训练损失记录。
调整时再看:日志记录更少,是否代表训练更新更少
若只把日志间隔从 5 改成 10,正常记录点会从 5、10、15……变成 10、20、30……,每条训练 Loss 的汇总区间也随之改变。更新参数的次数没有因此减半,只是观察得更稀疏了。日志点少,可能漏掉短暂波动;不要把记录点更少、曲线看起来更平滑误读成模型学得更稳。
读到这里,先确认进度在增长、Loss 为有效数值、学习率按预热与衰减安排变化。某一步的精确记录时序需要时再查。
查日志时再看:预热第 5 步的学习率为什么是 4e-5
配置是 5e-5,第 5 步为什么记录 4e-5? 本轮使用 5 步预热,日志记录本次更新所用的学习率。开始第 5 次更新前,调度器已推进 4 次,所以学习率为 5e-5 × 4/5 = 4e-5;这次更新完成后,调度器才推进到 5,为下一次更新准备 5e-5。这是本轮 Transformers 5.8.0 的记录与调度顺序。
日志还可能带有 grad_norm,它是梯度整体大小的一个汇总量,可用于排查梯度异常。
遇到异常时,先分清是哪一种。 可以按下面的现象选择检查方向:
| 看到的现象 | 首先检查什么 |
|---|---|
| Loss 在有限数值之间上下波动 | 结合多个记录点与验证表现判断;不同批次难度不同,一次上升不能证明训练失败 |
Loss 或梯度反复出现 NaN、Inf | 保留首次异常附近的日志和配置,检查数据、有效答案标签、学习率与计算精度;不要靠增加 epoch 解决 |
grad_norm 偶尔很大 | 对照异常批次和前后记录;这个数受模型、数据和统计方式影响,没有跨任务通用的正常阈值 |
报错包含 CUDA out of memory | 按第 33 章的报错阶段检查显存占用;它与数值变成 NaN 是不同问题 |
NaN 表示运算没有得到有效数值,Inf 表示无穷值,可能与数值溢出等问题有关。先回看第 4.2 节的实际输入、截断后答案及 labels,确认仍有参与计算的答案位置;再核对精度与原配置。保留出问题的版本,每次只改变一个候选因素,才能判断哪项处理有效。若异常持续,应停止本次运行后排查,不把保存出的权重直接作为有效结果。
排查时再看:梯度裁剪、FP16 与日志里的异常值
梯度裁剪是在更新参数前,限制梯度整体大小的一种方法。以本课普通单卡训练使用的范数裁剪为例,当梯度整体大小超过 max_grad_norm 时,程序按比例缩小梯度;它限制的是梯度,不是把 Loss 压到这个数值。
本课 Transformers 5.8.0 的 grad_norm 记录来自裁剪前的范数。因此,假设阈值为 1.0、日志显示 4.0,并不说明裁剪没有生效。这里的数字是教学示例;裁剪与记录顺序可查对应版本源码。
FP16 的数值表示范围有限。混合精度训练通常使用梯度缩放,先放大损失以减轻很小的梯度下溢,再在更新前还原梯度的尺度;它与裁剪处理的环节不同,也不能修复错误数据或保证任何模型都适合 FP16。精度排查仍需遵守第 31 章的显卡兼容条件,不能在 V100 上直接改用 BF16。详见 PyTorch 混合精度说明。
还要注意,训练工具可能过滤日志汇总中的非有限 Loss。本课版本的 logging_nan_inf_filter 只影响日志统计,不负责修复梯度;曲线看起来正常,也不能替代对异常日志和实际回答的检查。具体定义见训练参数源码。
9.2 训练损失与验证损失
每处理一批训练数据,程序会计算损失并参与参数更新;验证时则使用另一份数据计算损失,不执行参数更新。
本次 eval_steps: 50,表示每完成 50 个更新步,就用全部 200 条验证样本做一次检查。因此正好在每轮结束时各有一次验证:
| 更新步 | 训练进度 | 验证 Loss |
|---|---|---|
| 50 | 第 1 轮结束 | 1.7238 |
| 100 | 第 2 轮结束 | 1.5951 |
| 150 | 第 3 轮结束 | 1.5880 |
这三次验证使用同一份数据。从第 50 步到第 100 步,验证损失降低约 0.1287;再到第 150 步,降低约 0.0071。后 50 步的改善幅度变小,但三个点还不能证明继续训练一定更好或已经过拟合。
这张表支持的判断是:本轮训练中,后面的检查点在同一验证集上的损失更低。 若想判断多练一轮是否值得,下一步应让这些检查点在相同生成设置下回答同一批验证输入,检查漏词、名称与格式。多花了计算时间,任务错误是否也减少,需要这一步来回答。
例如,假设候选甲的验证 Loss 更低,候选乙在关键词检查中更少漏掉完整名称,该怎样选?先确认两者使用相同验证输入、模板和生成设置,再按预先确定的任务验收要求比较。“按 Loss 选出的最佳检查点”只表示它在这个选择指标上最好。 若改为按任务指标选型,应记录新的选择依据,并留待独立测试验收。
查日志时再看:最后一条 Loss 与全程 train_loss 为什么不同
训练结束还会出现 train_loss。它和中途日志里的 loss 不是同一个统计区间:
| 记录 | 本次数值 | 统计含义 |
|---|---|---|
第 150 步的 loss | 1.4353 | 最近一个日志区间的训练损失 |
结束汇总的 train_loss | 1.8547 | 全程训练损失的汇总值 |
第 150 步的 eval_loss | 1.5880 | 这一轮验证数据的损失 |
因此,“最后一条 Loss 是 1.4353,为什么结束统计又是 1.8547”并不矛盾;前期较高的损失也计入全程统计。它们不是百分比,也不是关键词准确率。
表中数值保留四位小数。需要复算时,可在配套 results/keywords-clean/training/ 中查看 trainer_state.json 和 train_results.json;阅读时先分清各字段的统计范围。
9.3 Loss 曲线与常见现象
下面分别是示例训练的训练曲线和验证曲线:
训练曲线:每 5 个更新步记录一次。

验证曲线:只在第 50、100、150 步实际检查,共三个记录点。

_验证点之间的连线便于观察趋势,期间没有额外的验证记录。_
曲线横轴表示训练进度,纵轴是 Loss。每张图里的浅色 original 是原始记录,深色 smoothed 是平滑后的线,两条线都来自同一类损失,并非一条训练、一条验证。平滑只是便于观察趋势,没有额外做一次训练或验证。两张图的纵轴范围不同,不能直接用线条的陡峭程度比较下降幅度。
先观察整体趋势,再回到日志定位发生变化的位置:
_图:教学示意,不是本次训练曲线。蓝色实线表示训练 Loss,橙色虚线表示验证 Loss;没有画验证线的两行,只讨论训练日志中的现象。_
不要给所有任务规定“Loss 必须降到 0.1”之类合格线。这里比较的是模型对参考答案的预测情况;实际关键词还需要单独检查。
Loss 下降以后,还要检查实际回答。 示例 Adapter 对一篇矿山降温文章能按分号格式输出,但相对参考答案仍漏了“制冷降温”“焓差”等词。第 32 章会逐项拆解这条回答,再通过批量评估判断问题是否普遍。
9.3.1 过拟合的判断与处理
先练习读一组变化。下表是为讲解而构造的数字,不是真实运行结果,也不是判断合格的分数线。 假设每轮结束都按相同方式统计损失,并使用同一份验证集:
| 训练进度 | 训练 Loss | 验证 Loss | 可以观察到什么 |
|---|---|---|---|
| 第 1 轮结束 | 1.8 | 2.0 | 留下第一次检查结果 |
| 第 2 轮结束 | 1.4 | 1.6 | 两边都下降,验证数据上的预测也在改善 |
| 第 3 轮结束 | 1.1 | 1.7 | 训练继续改善,验证开始变差,需要继续核对 |
| 第 4 轮结束 | 0.9 | 1.9 | 验证连续变差,不应只因训练 Loss 更低就选择第 4 轮 |
第 2 轮以后,模型更贴近训练题的答案,验证题的损失却反而上升。这是需要检查过拟合的信号。重点是同一训练过程中两边的变化趋势,不是要求两个 Loss 必须相等,也不是用某一次差值下结论。
遇到这样的走势,按下面的顺序检查:
- 先确认比较条件没变。 是否一直使用同一份验证数据、同一套模板与预处理?文章或参考答案是否被截断?先排除数据与处理错误。
- 再看实际回答。 用较早和较晚的检查点,在相同生成设置下回答同一批验证输入。对照参考答案,检查新出现的漏词、多词和格式错误,不只盯着两条曲线。
- 根据验证结果调整。 如果较晚的检查点在验证损失和实际回答上都更差,可以保留较早的检查点,减少训练轮数;再回查训练数据是否重复过多、题材过于单一或答案有误,而不是继续加轮数。
这些选择都使用验证集,不拿测试集反复挑检查点。测试集仍按第 29 章的约定,留给方案确定后的最终比较。
回到本章的真实日志示例:三次验证 Loss 都在下降,不能把上表后两轮的现象套在它身上。下降幅度变小,也不等于已经过拟合。下一节再看示例配置怎样保存和选择检查点。
9.4 训练检查点与 Adapter
每隔一段时间保存训练状态,得到的存档就是训练检查点(checkpoint)。本次 save_steps: 50,实际保存了 checkpoint-50、checkpoint-100 和 checkpoint-150,分别对应完成 50、100、150 次更新时的状态。
把第 9.2 节的验证表与存档对上,就知道为什么同时设置“验证”和“保存”:第 50、100、150 步各有一份验证结果,也各有一份可加载的模型存档。
如果把验证、保存间隔都从 50 改成 25,这次 150 步训练就会在 25、50、75、100、125、150 步检查并存档。候选检查点更密集,验证计算、文件写入和保留存档的磁盘开销也可能增加。两种频率都不直接改变每次参数更新的学习率或 batch;调整时还要满足工具对最佳检查点选择的间隔要求,第 31 章保持二者一致。
配置还设置了 load_best_model_at_end: true:训练结束后,按 eval_loss 选择验证损失最低的检查点。本次最低值恰好出现在第 150 步,因此选中 checkpoint-150。如果最低值出现在较早的检查点,就应按所设规则选它,而不是固定选择最后一份。
跟做时,检查点保存在第 31 章配置的训练输出目录中。先认识两类内容即可,具体文件检查与下载留在第 31 章:
adapter_model.safetensors和adapter_config.json:保存 LoRA 增量权重与配置,加载时需要匹配的基础模型。checkpoint-*:除相应 Adapter 外,还保存优化器、调度器、训练状态等,用于恢复训练或检查过程;只复制一份增量权重不等于完整续训存档。
示例 Adapter 权重约 20 MB,只保存新增的 LoRA 权重,不代表完整 Qwen 模型只有这么大。下载和保存时,仍需区分“用于加载的 Adapter”和“用于继续训练的完整检查点”。
推理时可以让基础模型与 Adapter 一起加载,也可以在支持的配置下把增量合并到对应基础权重,再导出完整模型:
图中展示的是两种产物的关系。第 31 章先完成训练和文件保存,第 32 章再实际加载、比较与导出模型。
章节思考题:
- 关键词 SFT 中,模型在预测什么?Loss、梯度和优化器怎样把参考答案用于参数更新?
参考思路: 模型根据文章、任务说明和前面的参考答案片段,预测后续 token。交叉熵用模型给正确 token 分配的概率衡量损失;反向传播据此求可训练参数的梯度,优化器再按规则更新参数。Loss、梯度由程序计算,学习率等设置由我们配置;训练并不是先生成整段答案,再数错了几个关键词。
- 本课关闭 train_on_prompt,为什么用户文章仍必须保留?如果参考答案中混入了无依据关键词,Loss 降低又说明什么?
参考思路: 关闭该设置表示用户内容对应的预测位置不直接计入损失,文章仍作为上下文参与计算、占用序列长度。错误参考也会成为学习目标,训练可能提高那些错误词的预测概率。因此 Loss 降低只说明更贴近当前训练目标,还要核对参考答案质量和实际生成效果。
- 单卡、不打包、1,600 条样本均保留,batch 为 4、梯度累积为 8、训练 3 轮:有效批次和总更新步数各是多少?只把 batch 改为 2 后,怎样维持原更新安排?
参考思路: 原有效批次为 4×8=32,每轮 1,600÷32=50 步,3 轮共 150 步。只减 batch 时,有效批次变成 16,总步数变成 300;每 50 步验证也变为每半轮一次。将累积同步改成 16,可恢复有效批次 32 和总计 150 步,但耗时与数值结果仍需实际比较。
- 学习率、预热和衰减分别控制什么?配置填了 learning_rate=5e-5,为什么日志中的学习率仍会变化?
参考思路: 学习率控制参数更新步长;预热让初期学习率从较小值逐步升高,衰减让后期学习率逐步降低。本课同时设置 cosine 调度和 5 个预热步,5e-5 是预热结束时达到的值,之后按调度变化。调大学习率不等于学得更好,是否合适要结合训练稳定性与验证回答判断。
- 训练显存主要用在哪里?Adapter 文件很小,为什么仍可能显存不足?梯度检查点又节省哪部分?
参考思路: 主要包括模型权重、梯度、优化器状态和计算中间结果,此外还有运行开销。Adapter 文件只反映部分训练产物,不包含整场计算所需的空间。梯度检查点少保存一些中间结果,需要时重算,以额外计算换显存;它与按步数写入磁盘的训练检查点不是同一件事。
- FP32、FP16、BF16 保存一个数分别占多少字节?为什么启用 FP16 不等于整场训练显存减半,也不同于 4-bit 量化?
参考思路: 分别占 4、2、2 字节;FP16 与 BF16 的数值范围和精度分配不同,选择时还要检查硬件支持。混合精度让不同计算或状态使用不同精度,部分状态仍可能保留 FP32,整场训练也有其他占用。4-bit 量化则用更少比特近似保存基础权重,不表示全部训练计算或 LoRA 参数都变成 4 位。
- 全参数微调、LoRA 和 QLoRA 分别更新哪些参数?LoRA 冻结原权重后,为什么仍能改变输出,又为什么需要基础模型?
参考思路: 全参数微调更新原模型参数;LoRA 冻结基础权重,主要训练新增的低秩分支,分支结果与原层结果相加后影响输出。QLoRA 在此基础上低位量化基础权重,仍训练 LoRA 参数。LoRA 减少大量基础参数的梯度和优化器状态,但基础模型仍参与计算;量化进一步节省权重存储,也引入近似误差。
- 配置中的 rank=8、alpha=16、target=all 和 dropout=0 分别表示什么?为什么增大 rank 不一定让效果更好?
参考思路: rank 控制分支的中间宽度,标准 LoRA 按 alpha/r 缩放增量,此处为 2;target 决定在哪里加分支,all 指本工具识别的适用线性层;dropout=0 表示不随机遮住分支输入。它们改变的部分不同。增大 rank 会增加可训练参数,却不保证任务效果提高;调整时要记录其他设置并比较验证结果。
- cutoff_len 限制的是字符数还是 token 数?当前最长训练序列为 957 token,上限从 2048 改成 4096 或 512,分别需要怎样判断?
参考思路: 它限制预处理后的 token 序列长度,序列包括任务、文章、答案与模板标记。2048 已能容纳当前样本,改成 4096 不会凭空增加内容,显存还取决于实际长度与填充;降到 512 可能截断长样本,要检查文章、答案和保留条数。更换数据、模板或分词器后,应重新统计。
- 训练 Loss 与验证 Loss 分别反映什么?如果训练 Loss 继续下降,验证 Loss 连续上升,怎样选择后续方案?
**参考思路:** 前者来自参与学习的数据,后者来自不用于参数更新的验证数据。持续背离可能提示过拟合,也要先核对数据和预处理。比较较早与较晚检查点在同一批验证输入上的回答,再决定是否减少轮数或调整训练;更低 Loss 不能直接证明关键词更准确,测试集不用于反复调参。
进阶练习:LoRA 计算与参数变化
- 常见默认 LoRA 初始化中,A 随机、B 为零,为什么分支开始时不改变原输出?A、B 都为零是否等价?
参考思路: B 为零时乘积增量为零,但训练可以先更新 B,随后逐步学习分支。两矩阵都为零,会使这个乘积结构的常规梯度更新无法把分支学起来。这里讨论第 6.2 节的常见默认方式,其他初始化要按相应设计理解。
- 把 rank 从 8 改成 16,alpha 保持 16,标准 LoRA 的缩放系数怎样变化?怎样保持原系数?
参考思路: alpha/r 从 2 变为 1;若希望仍为 2,可把 alpha 改为 32。rank 增大还会增加分支参数量,保持缩放系数不代表输出或任务效果不变,因此仍需比较验证结果与开销。
- 总计 150 步,预热从 5 步改成 15 步,训练安排会怎样变化?怎样判断这项调整是否有帮助?
参考思路: 预热包含在总预算内,总数仍为 150 步,后续衰减阶段从 145 步变成 135 步。学习率上升过程拉长是确定变化;是否更稳定、任务效果是否更好,要在相同起点和其他条件下比较日志与验证回答。
- LoRA dropout 从 0 改成 0.1,会删除 A、B 的部分权重吗?推理时是否继续随机丢弃?
参考思路: 它在训练时随机遮住部分分支输入,A、B 权重尺寸和参数量不变,正常推理时关闭。这个设置有时有助于缓解过拟合,也可能影响有限训练中的学习;它不能修正错误标注,也不是节省显存的开关。
本章小结:
- 模型先根据上下文预测 token,Loss 衡量预测,反向传播计算梯度,优化器执行参数更新。本课主要直接监督助手答案的位置,用户文章仍是参与计算的上下文。
- batch、梯度累积和 epoch 决定样本怎样分批、多久更新一次和总共遍历几轮;学习率控制更新步长,预热与衰减安排它随进度的变化。
- 训练显存包括权重、梯度、优化器状态和中间结果,不能只按权重大小估算。混合精度为不同计算或状态选择适用精度;长度要按完整序列检查,梯度检查点则用重算减少部分中间结果的存储。
- 全参数微调更新原模型参数,LoRA 主要训练新增分支,QLoRA 进一步量化基础权重。rank 控制分支宽度,alpha 控制缩放,target 决定作用位置,dropout 在训练时随机遮住部分分支输入。
- 训练与验证 Loss 帮助观察学习过程,检查点保存阶段结果,实际回答用于检查任务表现。配置是否合适,需要结合数据、资源与验证结果判断,不能只凭一条曲线或 Adapter 文件大小下结论。
建议下一步: 回到第 8.1 节,解释每项配置的用途,算出有效批次与更新步数,再说明训练会留下哪些文件、怎样检查效果。然后进入第 31 章,把这些理解用于环境准备、配置填写和真实训练日志的核对。
LLaMA Factory环境搭建与微调实战
31 - LLaMA-Factory 环境搭建与微调实战
本章课程目标:
- 能按顺序完成环境准备、页面连接、数据登记、参数配置、训练启动与结果保存,分清本机和远端的职责。
- 能检查 GPU 实际计算、数据预览与模型模板配置,判断是否具备启动训练的条件。
- 能把 WebUI 设置对应到 YAML 和日志,解释独立验证集、批次与训练方式等关键设置。
- 能观察训练进度,区分页面连接故障与训练中断,并根据日志和状态选择检查点。
- 能整理 Adapter、配置、数据版本与实验记录,检查备份完整性,分清推理加载与完整续训所需材料。
学习建议: 按正文顺序完成“准备环境 → 连接页面 → 登记并预览数据 → 配置训练 → 启动并观察 → 保存结果”。每一步先说明正在操作本机还是远端,再核对完成标志;例如看见 GPU 后还要运行 Python 计算检查,上传数据后还要预览。先用主线题复述正常流程,再处理连接、检查点与恢复问题,最后填写第 9.5 节实验记录。
1、实验任务与环境
第 29 章已经准备好关键词训练数据,第 30 章也解释了怎样设置训练参数。本章把这些文件和参数交给训练工具,得到一份能在下一章加载的 LoRA Adapter。
本章使用 AutoDL + LLaMA-Factory:AutoDL 提供带 GPU 的远程机器,LLaMA-Factory 提供训练程序和 WebUI(浏览器操作页面)。你在自己电脑的浏览器里填写参数,真正的模型下载和训练都在 AutoDL 实例中完成。
flowchart TB
A["第 2~4 节:准备环境<br/>配置 AutoDL → 安装工具 → 连接 WebUI"]
B["第 5~7 节:准备训练<br/>选择模型 → 预览数据 → 填写参数"]
C["第 8~9 节:训练与保存<br/>检查命令 → 启动一次训练 → 下载结果"]
A --> B --> C
本机与 AutoDL 实例的分工如下:
| 位置 | 本章的工作 | 命令在哪里执行 |
|---|---|---|
| 自己的电脑 | 打开控制台和训练页面,保存资料备份 | 数据清洗在本机完成;SSH 隧道也在本机建立 |
| AutoDL 实例 | 安装工具、下载模型、读取数据、执行训练 | JupyterLab 终端 |
后文的 /root/autodl-tmp 是 AutoDL 实例内的数据盘目录,相关命令在 JupyterLab 终端执行。
2、AutoDL 实例配置
第 7 章的 AutoDL 部署操作已经介绍过这个平台。本章在 AutoDL 算力市场创建单卡实例,完成 Qwen3-0.6B 的 LoRA 训练。
如果已经有可用实例,不需要另外租赁。 先对照下面的配置,再从第 2.5 节进入 JupyterLab。只有尚未准备实例的读者,才需要完成创建步骤。
2.1 单卡与显存选择
下面这组硬件和环境用于本章的 keywords-clean 训练。先准备实例与工具,再上传第 29 章清洗、划分后的数据。
| 项目 | 示例环境 | 跟做时看什么 |
|---|---|---|
| GPU 数量 | 1 张 | 不需要多卡 |
| GPU 型号 / 显存 | Tesla V100-PCIE-32GB / 32 GB | AutoDL 页面显示为 V100-32GB,终端再确认完整型号 |
| 数据盘 | 50 GB 起步 | 存放训练工具、数据和输出;模型缓存位置见第 2.5 节 |
| 创建时的基础镜像 | PyTorch 2.8.0 / Python 3.12 / CUDA 12.8 | 这是安装起点,不等于最终训练环境 |
| 最终训练环境 | Python 3.12.3、PyTorch 2.14.0+cu126 | 第 3 节解释安装与实际计算检查 |
| 训练计算类型 | FP16 | V100 跟做使用 FP16 |
示例显卡有 32 GB 显存;选择显卡时还要结合样本长度、batch 和工具版本估算实际占用。
使用已有的 RTX 3090、RTX 4090 或其他显卡时,先按第 3.4 节检查实际计算是否可用,再填写训练参数。
第 32 章会沿用这台机器检查回答和导出模型;若继续部署 vLLM 服务,还需按该章核对显卡兼容性,并使用独立的推理环境。
2.2 地区与主机选择
在市场页切换地区,选择有空闲卡的主机,将实际租用数量设为 1 张,并检查最终费用。

读图时按这个顺序检查:
- 顶部的 GPU 型号 和 GPU 数量:本课程第一轮只需要 1 张卡;
- 实例卡标题中的显存:图中
V100-32GB / 32 GB表示单卡显存为 32 GB; - “硬盘”区域中的 数据盘:图中为 50 GB,用于存放训练工具、数据和训练产物;
- “其它”区域中的 CUDA 版本:这是主机环境信息,随后仍需进入创建页选择镜像,并检查虚拟环境实际使用的 PyTorch/CUDA。
图中以 V100 为例说明选择位置;库存、价格和空闲卡数量会变化,租用时以控制台为准。
选读:关机后可能没有空闲 GPU,怎样提前考虑
关机后,主机上的 GPU 可能被其他人租用,影响下次带卡开机。选主机时可参考剩余卡数,但空闲卡不会自动为你保留。若暂时把市场页的数量筛选调到 2 或 4 来查看资源较多的主机,进入创建页后须将实际租用数量改回 1,并核对费用。
2.3 数据盘与基础镜像
镜像是预装了部分软件的环境起点;第 3 节会另建 .venv,安装并检查实际训练依赖。点击“可租”进入实例配置页,按下图选择单卡、50 GB 数据盘和基础镜像。AutoDL 基础镜像说明

对照截图,先找出下面四个位置:
| 页面区域 | 截图中的配置 | 跟做时注意什么 |
|---|---|---|
| 选择主机 | V100-32GB,单卡 32 GB | V100 跟做选择 FP16,并检查 PyTorch 是否包含 sm_70 内核 |
| GPU 数量 | 1 | 先以单卡跑通完整流程,不提前引入多卡变量 |
| 数据盘 | 免费 50 GB SSD | 存放 LLaMA-Factory、数据与训练输出 |
| 基础镜像 | PyTorch 2.8.0 / Python 3.12 / CUDA 12.8 | 镜像与虚拟环境的版本可能不同,安装后按第 3 节检查 |
确认配置无误后,到页面底部点击“创建并开机”。
2.4 无卡模式
下载模型、上传文件时,GPU 往往没有参与工作。如果这些准备要花较长时间,可以使用 AutoDL 的无卡模式降低准备阶段的费用:
- 在控制台找到刚创建的实例,确认没有正在执行的任务后关机。
- 对同一个实例选择“无卡模式开机”。
- 打开 JupyterLab,完成第 3 节的环境安装,再按第 5.1 节提前下载模型、按第 6 节上传数据;这些工作不需要 GPU。
- 等待下载、安装任务完成并保存文件,再将这个实例关机。
- 对同一个实例选择正常开机,重新打开终端并激活环境,然后检查 GPU、开始训练。
实例处于“已关机”状态时,在右侧点击“更多”,再选择“无卡模式开机”:

随后会弹出确认框,明确显示无 GPU、CPU 与内存配置,以及按小时计费的提示:

截图中的费用、实例编号和停机时间只代表截图时的页面状态。无卡模式适合做准备工作;需要训练或查看 GPU 显存时,必须回到带 GPU 的开机方式。
无卡模式并非免费,而且 CPU、内存配置也会降低。它适合文件准备,不适合本课程的 GPU 训练、vLLM 服务,也不适合需要大量内存或编译 CUDA 扩展的安装任务。遇到这类安装需求,就恢复正常开机再做。
2.5 JupyterLab 与工作目录
实例开机后,在 AutoDL 控制台的“快捷工具”中点击 JupyterLab。进入后点击文件面板上方的 +,在启动页的“其他”区域选择 终端。
接下来的安装命令都在这个远端终端中执行:
cd /root/autodl-tmp
pwd
df -h .
预期第一行输出:
/root/autodl-tmp
这里是课程的工作根目录,用于存放 LLaMA-Factory 源码、数据、训练输出和导出模型。Qwen3-0.6B 的下载示例使用 ModelScope 默认缓存 /root/.cache/modelscope/,位于系统盘;下载前检查剩余空间,需要改放数据盘时参考第 5.3 节。
下面是数据盘检查的实际输出。第一行是当前目录;表格最后一列是挂载点,两处都应为 /root/autodl-tmp。Avail 表示剩余可用空间,图中为 39G,自己的实例以实际读数为准。

/root/autodl-tmp/
├── LLaMA-Factory/ # 第 3 节克隆的训练工具
│ ├── data/keywords-clean/ # 第 6 节上传的数据与独立登记文件
│ └── saves/ # 本章产生的 Adapter、日志和 checkpoint
├── models/ # 可选:从缓存移到数据盘的基础模型
└── exports/ # 第 32 章导出的完整模型
检查点和导出模型会持续占用磁盘,放到数据盘便于管理。数据盘中的重要结果还要下载到本机;“保存系统镜像”不会自动打包数据盘内容。AutoDL 磁盘说明
3、LLaMA-Factory 安装
3.1 源码下载与版本选择
下面命令都在 AutoDL 的 JupyterLab 终端执行。先进入数据盘,克隆源码:
cd /root/autodl-tmp
git clone --depth 1 https://github.com/hiyouga/LLaMA-Factory.git
cd LLaMA-Factory
git fetch --depth 1 origin dced5f8804bfbf7109ef7c14401db6bd5cce7e53
git checkout --detach dced5f8804bfbf7109ef7c14401db6bd5cce7e53
最后两条命令把新克隆的仓库固定到课程使用的源码版本,便于对应页面、参数和示例结果。课程源码版本
源码下载很慢或出现 TLS 连接错误时
若 git clone 下载很慢,或出现 GnuTLS recv error (-110)、TLS 连接中断,在 AutoDL 的帮助文档 → 学术资源加速中查看当前命令,在同一终端执行后重试:
source /etc/network_turbo
设置会影响当前终端及它启动的程序,不会自动应用到其他终端。不再需要加速时,在同一终端关闭:
unset http_proxy https_proxy
支持的站点和当前命令以 AutoDL 学术资源加速说明为准。
3.2 项目目录与配置文件
克隆完成后,在 JupyterLab 左侧打开 LLaMA-Factory/。本章主要使用以下位置,其中课程数据、环境和训练结果会在后续步骤中创建:
LLaMA-Factory/
├── data/keywords-clean/ 第 6 节上传的课程数据与独立登记文件
├── .venv/ 第 3.3 节创建的 Python 环境
└── saves/ 训练生成的 Adapter、日志和检查点
训练设置也可以保存在 YAML 文本文件中。 打开本机课程附带的 案例与源码-4-微调/configs/keywords_clean_train.yaml,其中使用的是本章的 Qwen3-0.6B、关键词数据和 FP16 配置。下面摘出几项,加上中文注释:
# 训练阶段:SFT(监督微调),用输入和参考答案训练模型
stage: sft
# 微调方式:采用 LoRA,只训练新增的少量参数
finetuning_type: lora
# LoRA 的秩:设为 8,这个值会影响新增可训练参数的数量
lora_rank: 8
# 对话模板:使用 Qwen3 的非思考模式模板组织输入
template: qwen3_nothink
# 截断长度:每条训练样本的长度上限为 2048 个 token,包含输入和答案
cutoff_len: 2048
# 梯度累积步数:累积 8 个小批次的梯度后,再更新一次参数
gradient_accumulation_steps: 8
例如,lora_rank: 8 对应 WebUI 中的“LoRA 秩 8”。第 5~7 节会在页面填写这些设置,第 8.1 节再查看它们转换成的训练参数;选择命令行方式时,第 8.3 节会上传并使用这份课程 YAML。
选读:工具源码、官方示例与依赖清单
在 JupyterLab 的项目文件面板中,src/ 存程序源码,examples/ 存训练、推理和合并示例,requirements/ 存可选依赖清单:

下图打开的是工具自带的 examples/train_lora/qwen3_lora_sft.yaml。其中使用 Qwen/Qwen3-4B-Instruct-2507、identity,alpaca_en_demo 和 BF16,适合参考配置结构;本章运行时使用上面的课程 YAML。锁定版本的官方示例

安装时,uv pip install -e . 读取 pyproject.toml(官方源码)中的 torch、transformers、peft 等基础依赖。额外清单按用途安装:
| 清单 | 锁定版本中的内容 | 用途 |
|---|---|---|
requirements/metrics.txt(官方源码) | nltk、jieba、rouge-chinese | 部分文本评估功能 |
requirements/bitsandbytes.txt(官方源码) | bitsandbytes>=0.39.0 | bitsandbytes 量化;本轮不开量化 |
3.3 Python 环境与依赖安装
接下来使用 uv 创建独立的 Python 环境并安装依赖。先检查它是否可用:
uv --version
如果提示 uv: command not found,在当前 AutoDL 终端安装后再检查:
python -m pip install uv
uv --version
这是 uv 支持的 pip 安装方式;已有可用的 uv 时,不必重复安装。uv 安装说明
确认终端仍在 /root/autodl-tmp/LLaMA-Factory,再执行:
uv venv --python 3.12
source .venv/bin/activate
uv pip install -e .
uv pip install -r requirements/metrics.txt
uv pip install -e . 中的 . 表示当前目录,-e 表示以可编辑方式安装当前项目;因此要在仓库根目录执行,并保留这里的源码。metrics.txt 补充后续部分评估功能用到的依赖。
使用 V100 跟做时,安装下面的 CUDA 12.6 构建。 默认软件源可能选到不包含 V100 内核的包。等上面的安装结束后,在同一虚拟环境执行:
uv pip install --reinstall \
--index-url https://download.pytorch.org/whl/cu126 \
torch==2.14.0 torchvision==0.29.0 torchaudio==2.11.0
这三个包对应课程的 V100 训练环境。已有可用环境时,先检查版本,不必重复安装;准备完成后,按第 3.4 节实际执行一次小矩阵计算。
等待安装命令结束、终端重新出现可以输入命令的提示符后,再进入下一步。还在下载包时不要另外开一份安装进程。
每次新开终端时,都要重新进入项目、激活已有环境,让当前终端使用项目的 Python 和依赖:
cd /root/autodl-tmp/LLaMA-Factory
source .venv/bin/activate
提示符可能显示 (LLaMA-Factory),不一定直接显示 .venv;下一节会用 Python 的实际路径确认。
下载很慢或中断时,怎样重试安装
第一次执行 uv pip install -e . 时,uv 会先解析依赖,再下载 PyTorch、NVIDIA CUDA 等较大的安装包。即使速度较慢,只要进度条、已下载大小或“Preparing packages”的数量仍在变化,就说明命令还在工作,先等待当前命令结束。
前面执行的 source /etc/network_turbo 主要用于 GitHub、Hugging Face 等学术资源访问,不能据此认为 PyPI 下载一定加速。AutoDL 的说明也明确列出了它覆盖的站点范围。
如果下载总量连续几分钟完全不再增长,或终端已经报出网络错误,再按 Ctrl+C 结束这一次安装。在同一个已经激活 .venv 的终端中,临时改用 PyPI 镜像后重新执行下面两条安装命令:
source /etc/network_turbo
UV_DEFAULT_INDEX="https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" \
uv pip install -e .
UV_DEFAULT_INDEX="https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" \
uv pip install -r requirements/metrics.txt
UV_DEFAULT_INDEX 只对紧随其后的那一条命令生效,不会改写系统的全局软件源;这是 uv 官方支持的默认索引环境变量。镜像在某些网络环境下可能更快,但不保证始终更快;清华镜像站也提供了对应的 PyPI 与 uv 配置说明。uv 环境变量说明;清华 PyPI 镜像说明。
不要为了“快一点”加 --no-deps,也不要删除已经创建的 .venv;前者会漏装训练所需依赖。更不要让两条 uv pip install 命令同时运行,它们会争用网络、缓存和磁盘,反而更难判断进度。
3.4 版本与 GPU 计算检查
先确认实例已经带卡开机。在 AutoDL 终端进入项目、激活环境后,执行:
llamafactory-cli version
git rev-parse HEAD
nvidia-smi
前两条分别查看工具版本和源码提交;第三条查看 GPU 型号、显存与当前占用。课程使用工具版本 0.9.6.dev0、源码提交 dced5f8804bfbf7109ef7c14401db6bd5cce7e53。

图中的 Tesla V100-PCIE-32GB 与控制台的 V100-32GB 对应,32768 MiB 是显存容量。
看见显卡以后,还要确认当前 Python 能用它计算。 把下面整段复制到同一个终端执行。它只做一次小矩阵乘法,不加载模型,也不训练:
python - <<'PY'
import sys
import torch
print("Python 路径:", sys.executable)
print("Python 版本:", sys.version.split()[0])
print("PyTorch 版本:", torch.__version__)
print("PyTorch 构建使用的 CUDA:", torch.version.cuda)
assert torch.cuda.is_available(), "当前环境未发现可用 GPU,请先检查开机模式与安装结果"
print("GPU 型号:", torch.cuda.get_device_name(0))
print("GPU 计算能力:", torch.cuda.get_device_capability(0))
print("编译的 GPU 架构:", torch.cuda.get_arch_list())
x = torch.randn((1024, 1024), device="cuda", dtype=torch.float16)
y = x @ x
torch.cuda.synchronize()
print("计算完成,结果设备:", y.device)
PY
跟做时核对下面几项:
| 检查项 | 示例环境的结果 | 说明 |
|---|---|---|
| Python 路径 | 项目 .venv/bin/python | 正在使用项目环境,而不是系统 Python |
| PyTorch / CUDA 构建 | 2.14.0+cu126 / 12.6 | 与第 3.3 节的 V100 安装配置对应 |
| GPU 计算能力 | (7, 0),即 7.0 | V100 的硬件特性版本,不是 CUDA 软件版本 |
| 编译的架构 | 包含 sm_70 | 当前安装包包含这类显卡的内核 |
| 最后一行 | 计算完成,结果设备: cuda:0 | 实际 FP16 运算成功;cuda:0 表示第一张 GPU |

结果应包含 cu126、sm_70,并以 计算完成,结果设备: cuda:0 确认 FP16 运算成功。
进一步理解:为什么两处 CUDA 版本号可以不同
nvidia-smi 顶部的 CUDA Version 是驱动支持的最高 CUDA 版本;torch.version.cuda 是当前 PyTorch 构建使用的版本。两者不必显示同一个数字,真正需要通过的是上面的实际运算。AutoDL CUDA 说明
GPU 检查失败或出现 OMP_NUM_THREADS 提示时
检查失败时,先核对 Python 路径、实例是否带卡开机,以及第 3.3 节安装是否完整结束。解决报错、通过 FP16 运算后再启动 WebUI。
截图首行的 OMP_NUM_THREADS 提示涉及 CPU 线程变量;判断 GPU 是否可用仍看矩阵运算能否完成。若提示 GPU 内核不兼容,回查 PyTorch 构建及 sm_70 支持。
4、WebUI 启动与连接
4.1 启动 WebUI 服务
在 LLaMA-Factory 根目录执行:
cd /root/autodl-tmp/LLaMA-Factory
source .venv/bin/activate
llamafactory-cli webui
终端出现类似下面的信息时,说明服务已经在 AutoDL 实例中启动:
Running on local URL: http://0.0.0.0:7860
0.0.0.0:7860 表示服务在 AutoDL 实例中监听 7860 端口。保持这个终端运行,下一步通过 SSH 隧道从本机访问。

4.2 SSH 隧道连接
SSH 隧道是一条转发通道:浏览器访问自己电脑的 7860 端口,请求经隧道到达 AutoDL 实例中的 7860。本章使用本地转发,不使用控制台的“自定义服务”公网入口。

读图时沿中间的箭头看:本机浏览器 → 本机 7860 → SSH 隧道 → AutoDL 的 WebUI。图中的两种终端不要混用:本机终端负责保持隧道;JupyterLab 终端虽然也在浏览器里打开,命令却在 AutoDL 中执行。
Mac / Linux:在自己的电脑上打开终端。
在 AutoDL 控制台找到当前实例的 SSH 登录指令。假设它的结构为 ssh -p SSH端口 root@实例主机,把主机和 SSH 端口替换到下面的命令中:
# 将 SSH端口 和 实例主机 替换为控制台中的真实值,再执行
ssh -N -L 127.0.0.1:7860:127.0.0.1:7860 -p SSH端口 root@实例主机
这条命令在本机终端执行,不在 JupyterLab 里执行。按提示完成登录;输入密码时终端通常不会显示字符,输入完成后按回车即可。不要把密码写进命令或分享给他人。
| 命令中的位置 | 作用 |
|---|---|
第一个 127.0.0.1:7860 | 自己电脑上供浏览器访问的入口 |
第二个 127.0.0.1:7860 | AutoDL 实例中 WebUI 的地址 |
-p 后的 SSH 端口 | 来自控制台登录指令,通常不是 7860 |
-N | 只建立转发,不打开远端命令行 |
登录后终端保持等待,没有返回命令提示符,是隧道工作的常见状态。保持它打开,再在本机浏览器访问:
http://127.0.0.1:7860/
打开后确认页面是 LLaMA-Factory,并将语言切换为中文,继续配置模型和数据。
Windows:使用 AutoDL 图形化隧道工具
- 从 AutoDL SSH 隧道说明下载并打开工具。
- 填入正在运行 WebUI 的那台实例的 SSH 登录信息。
- 在“代理到本地端口”填写
7860,不是“代理到远程端口”。 - 开始代理并保持工具运行,在浏览器访问
http://127.0.0.1:7860/。
不同实例的主机、SSH 端口和登录凭据不同,请使用自己控制台里的信息。
页面打不开或连接中断时
按这三处检查:
- AutoDL:启动 WebUI 的终端是否还在运行,是否已经出现
7860监听信息。 - 本机终端:隧道是否还在运行,连接的是否是同一个实例;若提示端口被占用,先确认是否已有隧道,不重复启动。
- 浏览器:地址是否为本机
127.0.0.1:7860,打开的是否是目标服务。
本机另开终端可检查入口是否响应:
curl -I --max-time 5 http://127.0.0.1:7860/
返回 HTTP/1.1 200 OK 表示这个本机入口能够响应 HTTP 请求。接着打开浏览器,确认页面确实是目标实例的 LLaMA-Factory;状态码本身不能区分不同服务。
如果提示 Failed to connect 或页面出现 Connection errored out,先检查连接,不要重新安装或重新训练。只关闭本机隧道会断开访问,不等于远端训练已经停止。
重新连接后,按第 5~7 节核对模型、模板、精度和数据,再做启动前检查;页面可能恢复为不同的参数。
4.3 后台运行(选读)
需要关闭远端终端时,再了解后台启动
前台启动便于第一次看报错;准备长时间训练时,可以改为后台运行。如果还没有开始训练,先在运行前台 WebUI 的远端终端按 Ctrl+C 停止它,确认退出后,再在同一项目目录、同一虚拟环境中执行:
mkdir -p logs
nohup llamafactory-cli webui > logs/webui-keywords.log 2>&1 < /dev/null &
WEBUI_PID=$!
ps -p "$WEBUI_PID" -o pid,ppid,lstart,args
这里 nohup 用于让进程在终端断开后继续运行,& 让命令在后台执行,输出和报错写入这个日志文件。若已有同名日志,要换一个新文件名,避免覆盖旧记录。查看是否启动成功:
tail -n 50 logs/webui-keywords.log
看到 7860 启动信息后,通过第 4.2 节的隧道重新打开页面。不要同时再启动第二个 WebUI,占用同一个端口。
怎样确认终端退出后,服务确实还在?
$! 是刚启动的后台进程编号。记下 ps 输出中的 PID 和启动时间;在远端终端执行 exit 退出 shell、重新打开终端后,输入刚记录的 PID,再检查进程和服务:
read -r -p "输入刚记录的 WebUI PID:" webui_pid
ps -p "$webui_pid" -o pid,ppid,lstart,args
curl -I --max-time 5 http://127.0.0.1:7860/
比较退出终端前后的 PID 和启动时间,确认仍是同一个 WebUI 进程;再看 HTTP 是否返回 200 OK。下面两张截图展示这些检查位置,具体编号以自己的输出为准:


远端服务正常后,按第 4.2 节建立本机 SSH 隧道,再从浏览器确认能打开 LLaMA-Factory 页面:

网页能打开,只说明服务可访问。 训练前仍需完成第 5~7 节的模型和数据配置;如果训练已经开始,不要为了改启动方式按 Ctrl+C。后台运行只处理终端断开,实例关机仍会停止任务。
怎样停止后台 WebUI?
先确保 Train 页的训练已经结束或中断,Chat 页加载的模型也已卸载。停止 WebUI 服务与停止其中的训练是两件事;训练的中断入口见第 8.2 节。
在 AutoDL 终端列出 WebUI 进程:
ps -eo pid,ppid,lstart,args | grep '[l]lamafactory-cli webui'
核对命令包含 llamafactory-cli webui,启动时间与目标服务一致。在这个终端输入刚查到的 PID,再检查一次;不要照抄截图中的进程编号:
read -r -p "输入要停止的 WebUI PID:" webui_pid
ps -p "$webui_pid" -o pid,ppid,lstart,args
确认是目标服务后,才执行下面的停止命令。条件判断只接受大于 1 的进程编号,避免空值、0 或系统主进程被当成目标:
[[ "$webui_pid" =~ ^[1-9][0-9]*$ ]] && (( webui_pid > 1 )) && kill -TERM "$webui_pid"
ps -p "$webui_pid" -o pid,ppid,lstart,args
python - <<'PY'
import socket
with socket.socket() as s:
s.settimeout(2)
print("7860 连接检查:", s.connect_ex(("127.0.0.1", 7860)))
PY
这里用 Python 检查端口,无需额外安装网络工具。返回 0 表示仍可连接;非 0 表示连接未成功,还需结合 ps 判断目标进程是否退出。确认目标进程退出、端口不能连接后,再启动新的 WebUI;仍有占用时先核对遗留进程。关闭本机 SSH 隧道、退出 tail 或关闭网页,都不等于停止远端服务。
5、模型与对话模板配置
5.1 模型与微调方式
在 WebUI 的训练页面,按本课程的第一轮练习选择:
| 页面项目 | 本课程选择 |
|---|---|
| 模型名称 | 搜索 Qwen3-0.6B;本章界面示例中的选项叫 Qwen3-0.6B-Thinking |
| 模型路径 | 先确认是 Qwen/Qwen3-0.6B;下载后也可以填本地模型目录 |
| 模型来源 | ModelScope |
| 训练阶段 | SFT(监督微调) |
| 微调方法 | LoRA |
| 量化等级 | 不开启 |
“模型名称”是 WebUI 的显示名,下载用的仓库 ID 是 Qwen/Qwen3-0.6B。模型路径决定加载什么权重,对话模板决定怎样组织消息;模板在下一小节设置。
下图只截取页面上方的模型设置。按三行核对:第一行看模型名称、仓库 ID 和 ModelScope;第二行选 LoRA,检查点路径留空;第三行不开量化,并选择下一小节要用的 qwen3_nothink 模板。

V100 使用的 fp16 位于 Train 页的“计算类型”,第 7.3 节会和批次等参数一起填写,不在上面这三行中。
选择 ModelScope 作为下载来源后,可以按下面的方法提前下载模型,也可以在首次正式加载时由工具下载。
实操:提前下载模型,不启动训练
本步骤也可以在无卡模式下完成。先按第 3 节装好环境,在 AutoDL 的 JupyterLab 终端中执行:
cd /root/autodl-tmp/LLaMA-Factory
source .venv/bin/activate
df -h /root /root/autodl-tmp
下面的下载命令使用 ModelScope 默认缓存,通常位于系统盘 /root/.cache/modelscope/。先确认系统盘还有下载空间,再执行整段命令:
python - <<'PY'
from modelscope import snapshot_download
model_dir = snapshot_download("Qwen/Qwen3-0.6B")
print("模型下载目录:", model_dir)
PY
snapshot_download 下载模型仓库文件并返回本地目录,已有的同版本缓存会复用。ModelScope 下载接口
下载完成后,终端会打印「模型下载目录」。在 JupyterLab 文件面板中打开这个目录,确认有 config.json、权重文件和 tokenizer 文件。记下实际返回的路径:第 29 章打印模板、第 30 章检查预处理结果时,都需要它。
缓存仍在默认位置时,WebUI 和课程 YAML 可以继续填写仓库 ID Qwen/Qwen3-0.6B。如果需要把已下载的模型移到数据盘,接着看第 5.3 节;更换为 8B 等大模型前,应另行规划数据盘下载位置,不直接照用这个小模型的默认缓存方案。
5.2 对话模板与思考模式
第 29 章已经用图书馆示例解释了对话模板,并对照了模型路径与模板选项。这里把设置用于关键词训练:
| 页面项目 | 本次设置 |
|---|---|
| 对话模板 | qwen3_nothink |
| 思考模式 | 关闭,对应 enable_thinking=False |
关键词样本的助手答案只有关键词,不包含推理过程,因此本次训练不提供思考正文作为示范。模型显示名中的 Thinking 并不会替我们完成这些设置,仍要单独核对模板和思考开关。没有训练思考正文,不保证模型回答时一定不分析。 本版本 qwen3_nothink 不会因这个开关补入空思考区块;生成阶段的实测区别见第 32 章第 2.4 节。
消息边界与 <think> 标签的处理见第 29 章的模板说明,参与损失计算的位置见第 30 章的训练标签。
换模型或重新打开页面后,都要确认模板没有变回默认值。下一章先沿用这组设置复现已有结果,再单独阅读模板推理对照。如果当前版本找不到 qwen3_nothink,先回第 3 节核对工具版本,不用另一个相似名称代替。
5.3 本地模型路径(选读)
模型已下载完成,需要从数据盘加载时再展开
第一次使用仓库 ID 时,可以先保持上面的设置,不必提前移动尚未下载好的模型。只有模型已经下载完成、当前没有任务正在加载它,而且你需要把系统盘缓存整理到数据盘时,才做本节操作。
先在 JupyterLab 文件面板或终端检查两个位置:
ls -ld /root/.cache/modelscope/models
ls -ld /root/autodl-tmp/models
第一条应找到已下载的缓存目录;第二条若提示不存在,才符合下面这条移动命令的前提。两个目录都存在时,不要继续照抄移动命令,先查看已有内容。
确认源目录存在、目标目录不存在后执行:
mv /root/.cache/modelscope/models /root/autodl-tmp/
这样会得到:
/root/autodl-tmp/models/
这会移动整个 models/ 缓存目录。之后训练配置要改用移动后的本地路径。
在 models/ 下继续打开 Qwen3-0.6B 的目录,找到同时包含权重、config.json 和 tokenizer 文件的那一层。例如目录结构可能是:
/root/autodl-tmp/models/Qwen--Qwen3-0.6B/snapshots/master/
├── config.json
├── model.safetensors
├── tokenizer.json
└── tokenizer_config.json
模型路径就填这个同时包含配置、权重和 tokenizer 的目录。 它是权重文件的父目录,不是这个目录的上一层。缓存版本不同,外层目录名可能不同,以文件面板里实际存在的内容为准。
注意,在左侧文件面板里双击文件夹,不会改变已经打开的终端目录。若你的模型确实位于上面的示例位置,要在 AutoDL 终端单独执行 cd:
cd /root/autodl-tmp/models/Qwen--Qwen3-0.6B/snapshots/master
pwd
ls config.json tokenizer_config.json
路径不同时,把 cd 后的地址换成文件面板中找到的实际目录。确认 cd 成功,且 ls 找到了配置文件后,再把 pwd 的完整输出粘贴到 WebUI 的模型路径中;若 cd 报错,终端仍停留在原位置,此时不要复制 pwd。不要只复制到 models/ 的总目录,也不要复制到单个 model.safetensors 文件。
原来填 Qwen/Qwen3-0.6B 时,工具按仓库 ID 找模型;现在填完整本地目录时,工具从这个目录读文件。不要把本地路径填到“检查点路径”中,那里留给稍后训练出的 Adapter。
移动后如果仍填写仓库 ID,工具可能在默认缓存处重新下载一份。采用本地路径时,要同时修改训练 YAML 的 model_name_or_path,后续预测、导出也使用同一份模型;不能只改页面而仍运行旧 YAML。
不需要整理缓存位置时,继续使用仓库 ID 即可,跳过本节的移动操作。
使用仓库 ID 时,模型会从已下载的缓存加载;课程示例对应 /root/.cache/modelscope/models/Qwen--Qwen3-0.6B/snapshots/master。若改用数据盘的本地模型目录,后续训练、预测和导出也应指向同一份基础模型。
6、数据上传与登记
第 29 章已经生成 keywords-clean,本节直接使用其中清洗并划分好的数据,不再重新清洗或随机分组。上传之后,先检查文件,再让 LLaMA-Factory 读取登记信息,最后在页面预览。
6.1 数据上传与完整性检查
在 AutoDL 的 JupyterLab 文件面板进入 /root/autodl-tmp/LLaMA-Factory/data/,新建 keywords-clean 文件夹。打开它,点击文件面板上方的“上传文件”,选择本机 案例与源码-4-微调/processed/keywords-clean/ 中的六个文件;也可以把这些文件拖进当前文件面板。上传完成后应看到:
data/keywords-clean/
├── keywords_train.jsonl # 1,600 条,用于训练
├── keywords_validation.jsonl # 200 条,用于开发和选择检查点
├── keywords_test.jsonl # 200 条,留给第 32 章最终测试
├── dataset_info.json # 三份文件的登记
├── manifest.json # 条数、文件校验值、原始行号
└── cleaning_report.json # 自动处理明细与检查范围
若服务器已存在同名目录,先核对版本,不覆盖里面的数据。测试文件即使已上传,也不应加入训练或训练期间验证。
在 AutoDL 的 LLaMA-Factory 根目录检查上传内容:
cd /root/autodl-tmp/LLaMA-Factory
python - <<'PY'
import hashlib
import json
from pathlib import Path
folder = Path("data/keywords-clean")
manifest = json.loads((folder / "manifest.json").read_text(encoding="utf-8"))
for name, info in manifest["splits"].items():
file = folder / info["file"]
assert hashlib.sha256(file.read_bytes()).hexdigest() == info["sha256"], file
rows = [json.loads(line) for line in file.read_text(encoding="utf-8").splitlines()]
assert len(rows) == info["records"], file
print(f"{name}: {len(rows)} 条,校验通过")
PY
这里的 SHA-256 可以理解为文件内容的“指纹”:上传前后相同,才说明服务器读到的是这份数据。上面代码同时检查文件指纹和记录条数,正常结果应是:
train: 1600 条,校验通过
validation: 200 条,校验通过
test: 200 条,校验通过
校验失败时先检查上传是否完整、是否混入了别的版本,不修改训练参数来绕过。
下图中,三份数据均通过文件指纹与条数核对:

图中前三行对应数据校验结果;本步骤只核对指纹和条数。末行“输出目录尚未创建”不属于这段代码的输出,训练输出目录在第 8 节检查。
6.2 数据集路径与登记文件
本次 WebUI 的数据路径填写 data/keywords-clean。工具会读取这个目录内的 dataset_info.json,不需要修改 LLaMA-Factory 自带的 data/dataset_info.json。
上传的是文件,页面选择的是登记名。以训练集为例,工具会按下面的关系找到它:
因此,数据路径填文件夹,下拉框选 keywords_train;不要把 keywords_train.jsonl 填进数据路径,也不要把登记名当作另一个需要上传的文件。
清洗脚本已经登记了三项:
| 登记名 | 指向的文件 | 用途 |
|---|---|---|
keywords_train | keywords_train.jsonl | 训练 |
keywords_validation | keywords_validation.jsonl | 训练期间验证 |
keywords_test | keywords_test.jsonl | 方案确定后的测试 |
需要核对字段映射时:dataset_info.json 怎样读取样本
其中一项的结构如下;另外两项只更换登记名和文件名:
{
"keywords_train": {
"file_name": "keywords_train.jsonl",
"formatting": "sharegpt",
"columns": { "messages": "conversations" },
"tags": {
"role_tag": "role",
"content_tag": "content",
"user_tag": "user",
"assistant_tag": "assistant"
}
}
}
这段用于阅读字段,上传时仍使用清洗脚本生成的完整登记文件。按下面的对应关系理解它怎样读取一条样本:
| 配置字段 | 中文含义 | 本例告诉工具什么 |
|---|---|---|
file_name | 数据文件名 | 从同目录的 keywords_train.jsonl 读取样本 |
formatting | 数据格式 | sharegpt 表示按对话消息结构读取 |
columns.messages | 消息列表字段 | 样本中的消息保存在 conversations 中 |
tags.role_tag | 角色字段 | 每条消息用 role 标明角色 |
tags.content_tag | 内容字段 | 每条消息的文字保存在 content 中 |
tags.user_tag | 用户角色的取值 | role 为 user 时,识别为用户输入 |
tags.assistant_tag | 助手角色的取值 | role 为 assistant 时,识别为助手参考答案 |
在 JupyterLab 左侧打开刚上传的 dataset_info.json,展开 keywords_train,再展开 columns 和 tags。下图左边是六份实际文件,右边是登记内容:先对照 file_name,再核对 messages 和两个角色。

6.3 数据集预览与选择
填写数据路径后,在数据集下拉框选择 keywords_train,点击“预览数据集”。数量应为 1,600,每条都是 user → assistant,助手答案只包含分号分隔的关键词。

先看上方的 数量 1600,再在样例中找到 conversations、role: user 和 role: assistant。这个版本把样例显示为较长的文本;不必逐字阅读文章,重点确认中文没有乱码、角色对应正确、答案中的分隔符仍是英文分号。
预览验证集时,先清除训练集选项,再选 keywords_validation,确认数量为 200。预览结束后,训练页的数据集选择框恢复为只选 keywords_train;验证集在第 7.2 节单独指定,测试集留给最终评估。

预览用于核对文件、角色、中文显示和字段映射;实际 token 长度与参与损失计算的位置,还要按第 30 章“检查实际训练长度”核对,不能只看预览是否成功。
数据下拉框或预览出现问题时
- 下拉框没有
keywords_train。 先确认数据路径为data/keywords-clean,再在 JupyterLab 中打开这个目录,检查是否有dataset_info.json,其中是否登记了keywords_train。 - 能选中名称,但提示找不到文件。 查看登记项中的
file_name,确认它填写的是keywords_train.jsonl,且文件确实位于同一个数据目录。 - 打开后角色或内容不对。 对照 JSONL 中的
conversations、role、content,检查第 6.2 节的字段映射;本例应把user识别为输入,把assistant识别为参考答案。如果提示 JSON 格式错误,再打开报错对应的记录检查。
7、LoRA 训练参数配置
模型、模板和数据选好后,填写 训练页主参数、其它参数设置、LoRA 参数设置 三处;RLHF、多模态等区域保持默认。第 30 章已解释取值原因,这里照位置填写,最后在第 8.1 节确认设置确实传给程序;各处完整字段表供查阅。
7.1 训练方式与输出目录
沿用第 5 节的模型设置:SFT、LoRA、量化等级 none、qwen3_nothink,关闭思考模式。模型显示名保留 Qwen3-0.6B-Thinking,输出目录填写 keywords-clean;按这个显示名与微调方法,结果路径应为:
saves/Qwen3-0.6B-Thinking/lora/keywords-clean
若该目录已经存在,换一个新实验名,并同步更改后续预测、导出配置里的 Adapter 路径。仅想重新打开日志时,不要再次点“开始”。
本轮从基础模型新建 Adapter,因此上方“检查点路径”留空,不选择过去的训练结果。加载已有 Adapter 和从中断位置续训是另外的操作,不混入这次首次训练。
7.2 独立验证集配置
训练页填写:
| 页面项目 | 值 |
|---|---|
| 数据路径 | data/keywords-clean |
| 数据集 | 只选 keywords_train |
| 最大样本数 | 1600 |
| 验证集比例 | 0 |
这里有两处设置需要配合:主页面的“验证集比例”设为 0,表示不再从训练数据中切分;独立验证文件则在“额外参数”中指定。展开 其它参数设置,找到右侧的 额外参数 JSON 输入框,用下面完整内容替换原来的 {"optim": "adamw_torch"},不要在已有大括号后再拼一段:
{
"optim": "adamw_torch",
"preprocessing_num_workers": 4,
"eval_dataset": "keywords_validation",
"val_size": 0,
"eval_strategy": "steps",
"eval_steps": 50,
"per_device_eval_batch_size": 4,
"load_best_model_at_end": true,
"metric_for_best_model": "eval_loss",
"greater_is_better": false
}

这张局部截图对应上面的整个 JSON 输入框。填好后,先找到 eval_dataset 和 eval_steps 两行,分别核对验证文件的登记名与检查间隔;最终是否传给训练程序,还要看第 8.1 节的命令预览。
每 50 步在这 200 条验证数据上计算损失,并按较低的验证 Loss 选择检查点。它只是候选选择依据,不代表关键词内容一定更好;下一章还会检查实际回答。
查阅:额外参数中各字段的含义
按字段逐项对照:
| 中文名称与配置字段 | 本次取值 | 在这次训练中做什么 |
|---|---|---|
优化器optim | adamw_torch | 使用 PyTorch 的 AdamW 更新参数 |
数据预处理进程数preprocessing_num_workers | 4 | 使用 4 个进程处理数据,区别于一次训练几条样本 |
验证集eval_dataset | keywords_validation | 从登记表找到 200 条验证数据 |
自动验证划分val_size | 0 | 不再从 1,600 条训练数据中额外划分 |
验证策略eval_strategy | steps | 按参数更新步数安排验证 |
验证间隔eval_steps | 50 | 每完成 50 个更新步,运行一次验证 |
单卡验证批次per_device_eval_batch_size | 4 | 每张卡验证时每批处理 4 条,与训练批次分别设置 |
结束时加载最佳检查点load_best_model_at_end | true | 训练结束后加载选中的最佳检查点 |
最佳检查点的比较指标metric_for_best_model | eval_loss | 使用验证损失比较候选检查点 |
指标是否越大越好greater_is_better | false | 本例验证损失越小越好 |
已有独立 eval_dataset 时,val_size 必须为 0;eval_dataset 与非零 val_size 同时出现会报错。这里配置的是训练期间验证;“评估与预测”标签页是单独的操作入口。WebUI 参数合并、数据参数约束。
7.3 批次、学习率与训练轮次
先按第 6.3 节只选择 keywords_train,再填写 Train 页直接显示的参数。下面将同一张真实截图分区放大,按从上到下的顺序核对;窄屏可在图内横向滚动。
先看学习率、训练轮数、随机种子、最大样本数和计算类型。
再看长度、批次、梯度累积、验证比例和调度器。
查看完整训练参数截图,确认数据路径为 data/keywords-clean,训练集为 keywords_train。
照图填写时,用这张短表核对数值:
| 页面区域 | 本次填写 |
|---|---|
| 学习率、轮数、随机种子、最大样本数、计算类型 | 5e-5、3、42、1600、fp16 |
| 截断长度、批次、累积、验证比例、调度器 | 2048、4、8、0、cosine |
| 其它参数中的日志间隔、保存间隔、预热步数 | 5、50、5 |
需要查看英文键名或复习用途时,再展开对应表。
查阅:页面主参数与 YAML 字段对应关系
| WebUI 项目与配置字段 | 本轮值 | 含义 |
|---|---|---|
截断长度cutoff_len | 2048 | 输入和答案组成的训练序列最多保留 2,048 个 token |
单卡 batchper_device_train_batch_size | 4 | 每张卡每个小批次处理 4 条 |
梯度累积gradient_accumulation_steps | 8 | 每累积 8 批更新一次,有效批次为 32 |
学习率learning_rate | 5e-5 | 控制参数更新步长,本例作为预热结束时的学习率 |
训练轮数num_train_epochs | 3 | 训练集学习 3 遍 |
随机种子seed | 42 | 控制本轮训练中的随机初始化、打乱等过程 |
计算类型fp16、bf16 | fp16 | 对应 fp16: true、bf16: false,使用 FP16 混合精度 |
学习率调度器lr_scheduler_type | cosine | 预热后按余弦曲线降低学习率 |
预热步数warmup_steps | 5 | 前 5 个更新步逐步提高学习率 |
日志间隔logging_steps | 5 | 每 5 个更新步记录一次日志 |
保存间隔save_steps | 50 | 每 50 个更新步保存一次,与验证间隔一致 |
日志间隔、保存间隔和预热步数在 其它参数设置 中,不在上方的主参数行。按下图填写 5 / 50 / 5:

当 1,600 条训练样本都被保留,且不启用打包时:1600 ÷ 4 ÷ 8 = 50 步/轮,3 轮预计 150 步。这是根据配置计算的预期,最终还要核对实际预处理条数和启动日志。
随机种子可以理解为控制随机过程的一个编号,本轮记下 42 即可。第 29 章的数据划分已经写进文件,改变这里的种子不会重新划分三份数据;相同种子也不能保证不同硬件和软件版本得到逐位相同的结果。
若训练时显存不足,按第 8.5 节的告警说明区分发生阶段,再参考第 33 章处理。
7.4 LoRA 与其他选项
展开 LoRA 参数设置。下图上方的 rank、alpha 和 dropout 分别填 8、16、0;下方左侧是作用模块,右侧是附加模块。本次只在作用模块中填 all,附加模块留空。
查阅:LoRA 与其它开关的字段和用途
| 页面项目与配置字段 | 本轮值 | 作用 |
|---|---|---|
LoRA 秩lora_rank | 8 | 设置新增分支的中间宽度 |
LoRA 缩放系数lora_alpha | 16 | 配合 rank,按 alpha/r 缩放分支结果 |
LoRA 随机丢弃lora_dropout | 0 | 不随机丢弃分支输入 |
LoRA 作用模块lora_target | all | 在工具识别的适用线性层添加分支,以预览为准 |
序列打包packing | false(关闭) | 不把多条短样本打包成一条训练序列 |
学习提示词train_on_prompt | false(关闭) | 用户输入用于提供上下文,不作为预测目标计入损失 |
思考模式enable_thinking | false(关闭设置) | 实际处理取决于模板;本轮训练答案不含思考正文 |
实验报告平台report_to | none(不启用) | 不向外部平台上报,训练日志仍保存到本地 |
“附加模块”留空;LoRA 变体、量化、DeepSpeed 和 offload 本轮不启用。RoPE 缩放保持 none,加速方式保持 auto。
all 填在作用模块,不是附加模块。当前页面若把空的作用模块解释为默认 all,仍要核对预览中的 lora_target。量化等级为 none 时,旁边出现 bnb 不表示已经使用 QLoRA。
回到 其它参数设置,确认“序列打包”“学习提示词”和“启用思考模式”都没有勾选。对话模板选了 qwen3_nothink 后,也要单独检查思考模式开关。
8、训练启动与日志检查
完成启动前检查后,选择 WebUI 或第 8.3 节的 CLI(命令行)入口,同一轮只启动一次。
8.1 参数预览与启动前检查
在 Train 页面点击“预览命令”。它不会开始训练,而是把刚才的选择转换成训练程序接收的参数。先按三组核对:
- 题目与模型: 模型、模板及训练数据正确,独立验证集已传入,测试集没有参与。
- 训练安排: batch、累积、轮数和精度与第 7 节一致,验证与保存间隔相同。
- 结果位置: 输出到本轮新目录,没有误挂旧 Adapter 或恢复旧检查点。
点击下面的核对表,与页面预览逐项比较;这里检查是否生效,不再重新解释参数原理。
启动前展开核对:实际预览中的关键字段
dataset_dir = data/keywords-clean # 数据目录
dataset = keywords_train # 训练集登记名
eval_dataset = keywords_validation # 验证集登记名
val_size = 0 # 不再自动划分验证集
max_samples = 1600 # 最大样本数
per_device_train_batch_size = 4 # 每张卡每个小批次处理的样本数
gradient_accumulation_steps = 8 # 更新前累积的小批次数
num_train_epochs = 3 # 训练轮数
save_steps = 50 # 每隔多少个更新步保存检查点
eval_steps = 50 # 每隔多少个更新步验证一次
load_best_model_at_end = True # 结束时加载最佳检查点
template = qwen3_nothink # 对话模板
enable_thinking = False # 关闭思考模式
fp16 = True # 开启 FP16 混合精度
val_size 的默认零值有时不显示,但不能出现非零值。核对完整输出路径与第 7.1 节一致,且没有 adapter_name_or_path、resume_from_checkpoint;参数不符时回页面修正。
对照实际命令预览:模型与训练数据 → 验证集与 LoRA
下面的命令预览中,上半段是模型与训练数据,下半段是验证集与 LoRA 设置。


先在上半段核对 dataset_dir、dataset 和 fp16,再在下半段核对 eval_dataset、eval_steps 和 lora_target。
开始前确认三件事: 第 3.4 节的实际 GPU 运算已通过;第 6 节上传的文件校验无误;输出目录没有已有训练结果。
本章下文命令都以 keywords-clean 为实验名。复做时若改了输出目录,也要把查看日志、读取检查点和打包命令中的路径一起更改。
8.2 在 WebUI 中启动
- 在页面下方的“配置路径”填写一个新的文件名,例如
keywords-clean-webui-20260907.yaml,再点击 保存训练参数。确认页面出现“训练参数已保存至”及对应路径,不覆盖以前保存的同名文件。 - 确认预览命令正确后,在 Train 页点击一次“开始”。不再同时执行第 8.3 节的启动命令。
- 等待页面日志更新。首次运行可能先下载模型和处理数据,暂时没有 Loss 曲线是正常的。

输出目录存训练结果,配置路径存页面设置。保存后,页面应提示 训练参数已保存至:llamaboard_config/keywords-clean-webui-20260907.yaml,再到 JupyterLab 中确认文件存在。
这里保存的是 供 WebUI 重新加载的页面设置;CLI 启动使用第 8.3 节的课程训练 YAML。本版本启动 WebUI 训练时,还会把解析后的参数写入结果目录的 training_args.yaml。重新加载页面设置后,仍需核对模型路径、输出目录和命令预览。配置保存与启动实现
如果页面日志看不全,可以在另一个 AutoDL 终端读取本轮 WebUI 训练日志:
tail -n 80 /root/autodl-tmp/LLaMA-Factory/saves/Qwen3-0.6B-Thinking/lora/keywords-clean/webui_subprocess.log
tail -n 80 显示日志末尾 80 行。文件暂时不存在时,检查页面是否已创建训练进程、输出目录是否改过;CLI 日志位置见下一节。
需要提前结束训练时: 回到 LLaMA-Factory 的 Train 页底部,点击橙色“开始”右边的红色 “中断”,等待日志显示任务中断,训练进度不再增长。这里不是 AutoDL 控制台的关机按钮。

“中断”不保证额外保存一次检查点,已经保存了哪些文件要到输出目录确认;不要把当前进度直接当成已保存进度。若训练是按下一节在终端启动的,则到启动该训练的 AutoDL 终端按 Ctrl+C,不是在这个页面点“中断”。
8.3 使用 YAML 启动(可选)
改用终端启动时展开;已经从 WebUI 启动则跳过
使用课程训练 YAML 案例与源码-4-微调/configs/keywords_clean_train.yaml,按下面的步骤上传、检查并运行。
先在 AutoDL 终端准备配置和日志目录:
cd /root/autodl-tmp/LLaMA-Factory
source .venv/bin/activate
mkdir -p configs logs
在 JupyterLab 左侧打开 LLaMA-Factory/configs/,上传本机 案例与源码-4-微调/configs/keywords_clean_train.yaml。如果同名文件已经存在,先打开比较,不直接覆盖。
上传后查看内容,重点核对模型、数据路径、验证集和输出目录:
sed -n '1,100p' configs/keywords_clean_train.yaml
如果前面改用了本地模型路径,就在这份 YAML 的 model_name_or_path 中同步修改;页面上的修改不会自动写回这份文件。确认后,在同一个 AutoDL 终端运行:
(
set -e
if [ -e saves/Qwen3-0.6B-Thinking/lora/keywords-clean ] ||
[ -e logs/keywords-clean-train.log ]; then
printf '输出目录或日志已经存在,请先核对;本次不启动训练。\n'
exit 1
fi
OMP_NUM_THREADS=4 USE_MODELSCOPE_HUB=1 \
llamafactory-cli train configs/keywords_clean_train.yaml \
> logs/keywords-clean-train.log 2>&1
printf '训练命令正常结束,请继续检查日志和输出文件。\n'
)
前面的判断避免覆盖同名实验。USE_MODELSCOPE_HUB=1 指定模型下载来源,OMP_NUM_THREADS=4 设置 CPU 线程数;> ... 2>&1 把运行输出和报错一起写进日志。括号让这段检查在单独的子 shell 中执行,失败时不会关闭你的终端。
训练期间这个终端会等待,不一定持续显示文字,因为输出已经写入文件。保持它运行,在另一个 AutoDL 终端查看:
tail -n 80 /root/autodl-tmp/LLaMA-Factory/logs/keywords-clean-train.log
需要持续观察时,可把 -n 80 换成 -f;在查看日志的终端按 Ctrl+C 只会退出查看,不会停止另一个终端中的训练。本例没有使用后台启动,训练终端不要关闭;如果没有出现“训练命令正常结束”,先打开日志末尾查错。
8.4 启动日志与训练进度
启动后,按日志检查模型加载、数据处理与训练进度。自己的日志、检查点和下一章加载的 Adapter,都以第 7.1 节的本轮目录为准。 下方 WebUI 截图和 CLI 数字来自分别保存的两次演示,不能把它们混成自己的一次运行。
示例文件对应关系
文字日志、独立 Loss 图和第 32 章评估对应 CLI 示例 keywords-clean;WebUI 页面截图对应 keywords-clean-webui-20260908。两次示例使用相同数据与核心参数,各训练 150 步,但输出目录和 Adapter 分别保存。
配套文件均在 案例与源码-4-微调/results/ 下。自己跟做时,始终使用第 7.1 节设置的输出目录;可在配套文件说明中查找示例资料。
| 日志所处阶段 | 这时在做什么 | 主要看什么 |
|---|---|---|
| 模型下载、加载 | 准备基础权重和分词器 | 模型是不是 Qwen3-0.6B,是否有下载或依赖错误 |
| 数据读取、预处理 | 读取登记文件并处理样本 | 训练文件是不是 keywords_train,是否有格式错误或样本被丢弃 |
| 训练启动统计 | 汇总本轮训练安排 | 样本数、LoRA 参数量、batch、梯度累积、总步数 |
| step 开始增长 | 正在更新参数 | 进度、Loss、学习率、耗时和预计剩余时间 |
先看启动统计。
Num examples = 1,600
Num Epochs = 3
Num update steps per epoch = 50
Instantaneous batch size per device = 4
Total train batch size (w. parallel, distributed & accumulation) = 32
Gradient Accumulation steps = 8
Total optimization steps = 150
Number of trainable parameters = 5,046,272
核对样本数 1,600、总步数 150、有效批次 32,以及 LoRA 可训练参数量。同一日志记录的可训练参数比例约为 0.8395%。
WebUI 演示:启动与训练进度。

页面日志中也应出现这些统计,随后 step 与 Loss 持续更新。
若总步数与预期不符,检查选中的数据、验证比例、序列打包和实际保留的样本数。
进度条中的 当前步数/总步数 表示完成了多少次更新;时间区域一般同时显示已用时间与预计剩余时间。开始几步还在预热,速度可能不稳定,稍后再判断耗时。s/it 是每次迭代花费的秒数,it/s 则是每秒完成的迭代数,要连单位一起看。
本章每 5 步记录日志,每 50 步验证和保存;到验证步骤时,进度会暂缓,等待验证完成后继续。

图中的输出目录标明当前实验,Running 50/150 表示已完成 50 次参数更新,右侧显示已记录的训练 Loss。
8.5 Loss 曲线与结束状态
确认结束状态。 页面应显示“训练完毕”,再检查 trainer_state.json 中的完成步数、最佳检查点和输出目录中的 Adapter 文件。下图示例完成 150 步,选择了 checkpoint-150:

在自己的结果目录中打开 training_loss.png 和 training_eval_loss.png,分别查看训练和验证损失;具体读法见第 30 章的日志与曲线说明。数值记录在同目录的 trainer_log.jsonl 和 trainer_state.json 中。
再看 Loss 图与结束统计。


| 统计项目 | 示例结果 |
|---|---|
| 完成步数 | 150 |
| 最佳检查点的验证 Loss | 1.5880 |
| Trainer 记录的训练耗时 | 519.8399 秒,约 8 分 40 秒 |
| 最佳检查点 | checkpoint-150 |
表中耗时仅指 Trainer 统计的训练过程,不含安装、上传和备份。日志到达 150/150 后,还要加载最佳检查点、保存最终 Adapter,并完成验证;评估汇总保存在 eval_results.json。
完成检查: 进度到达末尾,日志给出结束与保存信息,再按第 9 节确认产物完整。关键词抽取效果留到第 32 章检查。
出现绘图指标缺失或显存分配告警时
No metric eval_accuracy to plot:表示没有eval_accuracy可供绘图;课程按eval_loss验证,不要求一定生成准确率曲线。检查实际记录的指标与训练结束状态。memory allocation failed with OOM:核对后续步数是否增长、进程是否继续运行,以及验证和保存是否完成。只看这一行不能判断整次训练是否中止;若进度停住或进程异常退出,按第 33 章排查。
若在处理训练批次时显存不足,可先将 batch 改为 2、梯度累积改为 16,维持有效批次 32;若在权重加载时就不足,检查模型大小、精度及已有占用。第 30 章计算器的 9.8 GB 仅为估算,自己的资源占用需按第 33 章的方法观察。
9、训练结果与备份
9.1 训练输出文件
训练结束后,在 JupyterLab 左侧依次打开 saves → Qwen3-0.6B-Thinking → lora → keywords-clean,也可以在 AutoDL 终端执行:
ls -lh /root/autodl-tmp/LLaMA-Factory/saves/Qwen3-0.6B-Thinking/lora/keywords-clean
按下面的用途检查并保留输出文件:
| 文件 | 查看或保留的用途 |
|---|---|
adapter_model.safetensors、adapter_config.json | 本轮 LoRA 权重与加载配置,一起保留 |
checkpoint-50/、checkpoint-100/、checkpoint-150/ | 不同更新步保存的训练存档 |
trainer_state.json | 查看训练进度、最佳检查点和对应验证损失 |
training_args.bin、启动 YAML | 保留训练设置;WebUI 会生成 training_args.yaml,CLI 上传的 YAML 需另外备份 |
llamaboard_config.yaml(WebUI 路线) | 页面设置记录,与训练参数文件区分开 |
trainer_log.jsonl、Loss 图、运行日志 | 复查训练和验证过程 |
train_results.json、eval_results.json | 汇总统计,不是逐条关键词预测答案 |
示例 Adapter 约 20 MB,保存的是 LoRA 增量权重;第 32 章会把它与对应的 Qwen3-0.6B 一起加载。
配套 results/keywords-clean/training/ 提供日志、曲线和统计,对应训练配置为 configs/keywords_clean_train.yaml,方便不启用 GPU 时学习。这是参考资料目录,不含 Adapter 权重及完整检查点,不能直接填入 WebUI 的检查点路径。
WebUI 路线保留结果目录中的子进程日志及自动保存的训练参数;CLI 路线还需保留第 8.3 节指定的 YAML 与重定向日志。
9.2 最佳检查点核对
在 AutoDL 的项目根目录读取 trainer_state.json,核对完成步数与选中的检查点:
python - <<'PY'
import json
from pathlib import Path
result_dir = Path("saves/Qwen3-0.6B-Thinking/lora/keywords-clean")
state = json.loads((result_dir / "trainer_state.json").read_text(encoding="utf-8"))
print("完成步数:", state["global_step"])
print("最佳检查点:", state["best_model_checkpoint"])
print("对应验证损失:", state["best_metric"])
PY
示例完成 150 步,最佳检查点为 checkpoint-150,对应验证损失约 1.5880。自己训练时按第 7.2 节的规则读取结果;最低损失也可能出现在较早的检查点。
加载 Adapter 检查回答需要相应权重和配置;完整续训还需要优化器、调度器和随机状态等,备份时应保留整个检查点目录。
备份时区分两种用途: 推理需要 Adapter 与加载配置;恢复训练还需要优化器、调度器等状态。按下面的方法检查自己的文件,不把只有权重的备份当作完整续训存档。
训练中断时再看:怎样从完整检查点恢复
先确认中断的是访问,还是训练。 浏览器断开、SSH 隧道退出,只说明访问出了问题。重新连接 AutoDL 后,先在终端检查进程和原来的训练日志:
ps -eo pid,etime,args | grep -E 'llamafactory|keywords'
进程列表可能同时出现 WebUI、训练子进程和搜索命令本身,要结合完整命令判断。按第 8.3~8.4 节查看自己那次运行的日志;如果训练仍在进行,就恢复访问并继续观察,不再启动第二份训练。进程退出且日志没有正常完成记录时,才考虑恢复。
再找最后一份完整写入的检查点。 例如,假设原计划训练 150 步,在第 120 步附近中断,最后完整保存的是 checkpoint-100,就只能从第 100 步的状态接续,尚未保存的更新需要重做。不能根据日志中的最大步数,虚构一份 checkpoint-120。
在自己那次训练的输出目录下检查文件。下面以本课单卡、普通 LoRA、AdamW 和 FP16 路线为例;将路径换成自己实际存在的检查点。这个命令只查看文件和 JSON,不加载模型:
python - <<'PY'
import json
from pathlib import Path
checkpoint = Path("saves/Qwen3-0.6B-Thinking/lora/keywords-clean/checkpoint-100")
required = [
"adapter_config.json", "adapter_model.safetensors", "trainer_state.json",
"optimizer.pt", "scheduler.pt", "rng_state.pth", "scaler.pt",
]
missing = [name for name in required if not (checkpoint / name).is_file()]
if missing:
raise SystemExit(f"缺少本路线的恢复文件,请核对原配置与备份:{missing}")
state = json.loads((checkpoint / "trainer_state.json").read_text(encoding="utf-8"))
print("检查点路径:", checkpoint.resolve())
print("已保存的更新步:", state["global_step"])
print("文件存在性检查通过;仍需在恢复时确认状态能够加载。")
PY
optimizer.pt 和 scheduler.pt 保存更新规则与学习率进度,rng_state.pth 保存随机状态,scaler.pt 保存本路线 FP16 梯度缩放的状态。其他精度或分布式路线的文件可能不同,不能直接套用这份清单。只找到 Adapter 文件时,可以另做推理或后续训练,但不能认定已拥有原训练的完整恢复状态。若当次使用了 save_only_model: true,之后再改成 false 也不能补回当时没有保存的状态。
最后准备恢复配置。 在 JupyterLab 中复制第 8.3 节自己当次实际使用的完整 YAML,在 LLaMA-Factory/configs/ 下另存为 train_keywords_resume.yaml。保持基础模型、数据及顺序、模板、LoRA 结构、批次、学习率、精度、种子和训练总轮次一致,只在副本中新增或替换下面两个字段;这是片段,不能单独作为训练配置:
output_dir: saves/Qwen3-0.6B-Thinking/lora/keywords-clean-resume
resume_from_checkpoint: saves/Qwen3-0.6B-Thinking/lora/keywords-clean/checkpoint-100
恢复输出目录选一个尚未使用的新目录,保留原目录供核对。原来的 num_train_epochs: 3 表示总共训练三轮,恢复时仍保持三轮;不要误填成“剩下还要练几轮”。本例从 100 步接续到原定 150 步。也不要额外把检查点填成 adapter_name_or_path:本节演示的是通过 resume_from_checkpoint 恢复原 SFT 状态。
确认原进程已退出、恢复文件完整、配置与环境相符后,先按第 8.3 节进入 AutoDL 项目根目录并激活原 .venv,再执行。下面沿用原下载来源与线程设置,并将恢复日志另存:
(
set -e
if [ -e saves/Qwen3-0.6B-Thinking/lora/keywords-clean-resume ] ||
[ -e logs/keywords-clean-resume.log ]; then
printf '恢复输出目录或日志已存在,请先核对;本次不启动训练。\n'
exit 1
fi
OMP_NUM_THREADS=4 USE_MODELSCOPE_HUB=1 \
llamafactory-cli train configs/train_keywords_resume.yaml \
> logs/keywords-clean-resume.log 2>&1
)
这个终端会等待训练,在另一个 AutoDL 终端用 tail -n 80 /root/autodl-tmp/LLaMA-Factory/logs/keywords-clean-resume.log 查看恢复日志。核对加载的检查点路径、恢复步数和后续进度,确认从已保存位置接续;出现状态加载失败或数据、配置不匹配时先停止排查,不把“程序能启动”当作恢复成功。更换数据、模型或训练方案,应另建实验,不冒充接续同一次训练。
参数传递方式见课程固定版本的 SFT 源码;仅保存模型与完整续训的区别见官方说明。
9.3 打包与下载
训练结果保存在 AutoDL 上,接下来打包并下载到自己的电脑。
先确认训练已经结束,再打包。 下列命令以本章路径为例,mktemp -d 会创建一个新的备份目录,不覆盖已有备份。
沿用本章主线、采用 第 8.2 节的 WebUI 方式时,在 AutoDL 终端执行。训练参数与 WebUI 子进程日志已在结果目录内:
cd /root/autodl-tmp/LLaMA-Factory
keyword_backup_dir=$(mktemp -d /root/autodl-tmp/keywords-backup-XXXXXX)
tar -czf "$keyword_backup_dir/keywords-clean-training-backup.tar.gz" \
saves/Qwen3-0.6B-Thinking/lora/keywords-clean \
data/keywords-clean
如果采用 CLI 启动,改用这一组打包命令
CLI 路线的启动 YAML 和运行日志在结果目录之外,需要一并打包。下面与上面的命令二选一:
cd /root/autodl-tmp/LLaMA-Factory
keyword_backup_dir=$(mktemp -d /root/autodl-tmp/keywords-backup-XXXXXX)
tar -czf "$keyword_backup_dir/keywords-clean-training-backup.tar.gz" \
saves/Qwen3-0.6B-Thinking/lora/keywords-clean \
data/keywords-clean \
configs/keywords_clean_train.yaml \
logs/keywords-clean-train.log
这里打包的是本轮训练输出和数据记录,不包含基础模型大权重,也不打包整个 .venv。基础模型仍需单独保留或按记录重新准备。
等打包命令无报错结束,在同一个 AutoDL 终端继续检查压缩包、生成校验文件,并打印备份目录:
cd "$keyword_backup_dir"
gzip -t keywords-clean-training-backup.tar.gz &&
sha256sum keywords-clean-training-backup.tar.gz > keywords-clean-training-backup.tar.gz.sha256
pwd
ls -lh
gzip -t 成功时通常不输出文字;如果报错,先停止下载步骤,检查是否有磁盘不足或文件缺失。pwd 打印的是刚创建的备份目录。
在 JupyterLab 文件面板打开这个目录,分别右键压缩包和 .sha256 文件,选择“Download / 下载”。把两个文件保存到本机同一个文件夹。分享操作截图或日志前,先去掉其中的实例连接信息和密码。
9.4 本机备份检查
以 Mac 为例,新建一个专门放本轮备份的本地文件夹,将刚下载的两个文件放进去。在这个文件夹打开终端;也可以先输入 cd ,再把文件夹拖入终端补全路径,按回车。
确认当前目录里有下载的文件后执行:
shasum -a 256 -c keywords-clean-training-backup.tar.gz.sha256
gzip -t keywords-clean-training-backup.tar.gz
tar -tzf keywords-clean-training-backup.tar.gz
第一条应显示压缩包名称和 OK,说明它与服务器上的文件一致;第二条检查压缩包是否完整;第三条列出包内文件,应能找到 Adapter、各检查点、三份 JSONL 和登记文件,CLI 路线还应有上传的 YAML 与运行日志。如果校验不是 OK,先重新检查下载文件,不把这个包作为可用备份。
文件校验通过,说明下载内容与服务器文件一致;模型能否从备份正常加载,还需实际检查。保存训练结果时,把模型名称、环境与训练配置一并保留,下一章再加载 Adapter 验证。
9.5 用一张记录卡串起本轮实验
为便于核对 Adapter 使用的数据、模板和训练配置,在本机存放本轮备份的文件夹中,新建 实验记录.md,汇总这些信息及已有文件的位置。
| 记录什么 | 从哪里填写 |
|---|---|
| 本轮名称与目的 | 用一句话说明要检查什么,例如“用清洗版数据完成首次关键词 LoRA 训练” |
| 模型与输入 | 基础模型 ID、下载版本或权重校验记录、分词器、聊天模板与思考设置 |
| 数据版本 | 三份数据的路径、条数和 manifest.json;另记所采用的标注规则版本 |
| 环境与训练配置 | LLaMA-Factory 版本、环境检查结果、本轮实际配置和日志的位置,包括随机种子 |
| 保存的结果 | 选定的检查点、选择依据、备份包名称及校验文件 |
| 实际回答与结论 | 第 32 章完成后补入提示词、生成设置、原始预测、评分报告及尚未解决的问题 |
训练结束时先填前五项。配置路径必须对应本轮实际运行的文件,不能只指向后来反复改过的通用 YAML。
交给第 32 章的是本轮 Adapter 和这张记录卡。 如果实验名改过,下一章的检查点选择、预测和导出配置也指向它;预测与评分再保存到本轮的新目录。这样能从一份答卷追回所用数据、模型和设置。下一章即使分数不好,也把原始输出和限制一起记下,作为下一轮比较的依据。
章节思考题:
- 从第 29 章的数据开始,到本章取得 Adapter,应按什么顺序操作?哪些工作在本机,哪些在 AutoDL 上完成?
参考思路: 先准备远端环境并验证 GPU 计算,启动 WebUI、建立本机 SSH 隧道,再上传登记数据、预览样本、配置模型和训练参数,最后启动训练并备份结果。模型加载与训练在 AutoDL 上执行,本机用于浏览器访问和保存备份。nvidia-smi 能识别显卡后,还应在当前 Python 环境完成本课的计算检查。
- 配置中的 model_name_or_path、stage=sft、finetuning_type=lora 和 template 分别决定什么?把页面设置交给训练前怎样核对?
参考思路: 它们依次指定模型、训练阶段、参数更新方式和对话模板。本课选择 Qwen/Qwen3-0.6B、SFT、LoRA 与匹配的模板,不能把模型名和模板名混为一项。预览实际命令或 YAML,核对这些字段以及输出目录、批次、学习率、精度等设置;启动后再从日志确认实际加载和使用的内容。
- dataset_info.json 中的登记名、文件路径和字段映射各有什么用?已有独立验证文件时,怎样选择数据并设置验证比例?
参考思路: 登记名供训练配置引用,文件路径指向具体数据,字段映射说明消息和角色怎样读取。预览确认训练集 1,600 条、验证集 200 条及内容正确后,训练页只保留 keywords_train,单独指定 eval_dataset 为 keywords_validation,val_size 为 0,表示不再额外切分。测试集不参与训练和检查点选择。
- 核对配置后,怎样启动并判断一轮训练是否完成?命令预览成功、进度显示 150/150 和文件保存成功,分别说明什么?
参考思路: 同一轮选择 WebUI 或 YAML 一种方式启动。预览成功只说明已生成配置;150/150 表示所显示的训练进度到达计划步数。还要确认日志正常结束,trainer_state.json 的进度与实际配置相符,Adapter 和相关文件已成功保存。当前数据与配置预计 150 步,其他配置应重新计算。
- 假设第 100 步的验证 Loss 最低,第 150 步正常结束,应怎样判断后续加载哪个检查点?
参考思路: 查看 trainer_state.json 的 best_metric 与 best_model_checkpoint,再核对保存和加载最佳模型的设置。按验证 Loss 选择时,应关注记录的最佳检查点,不能默认最后一步就是最佳版本;接着用相同验证输入比较实际回答。较低 Loss 只是选择依据之一,关键词质量在第 32 章继续检查。
- 本机突然打不开 127.0.0.1:7860,应该按什么顺序排查?若本轮用 YAML 启动,训练日志在哪里?
参考思路: 分别检查本机 SSH 隧道、远端 WebUI 和训练进程,并观察对应日志是否继续更新,确认中断的是哪一环。本章 YAML 启动方式把日志写入远端项目的 logs/keywords-clean-train.log;webui_subprocess.log 属于 WebUI 路线。训练仍在运行时不要重复启动,先恢复访问连接。
- 要让同事加载本次 Adapter 并复查结果,应该交接哪些材料?如果还要从中断处继续训练,要求有什么不同?
参考思路: 保存 Adapter 权重与配置、匹配的基础模型及版本、Tokenizer 和模板信息、实际训练配置、数据记录与日志,并核验下载的备份。复现回答还需输入和生成设置;完整续训另需检查点里的优化器、调度器、随机数等状态。对照第 9.5 节记录卡说明每份材料的位置与用途。
进阶练习:中断后的训练恢复
- 计划共 150 步,第 120 步时连接断开,最近完整检查点在第 100 步。确认训练进程已退出后,怎样安排恢复?
参考思路: 按第 9.2 节检查第 100 步存档是否保有所需训练状态,保留原数据与配置,再按完整检查点恢复。第 100 步之后未保存的更新需要重做;恢复后核对日志中的起点与总计划,不能再额外跑完整 150 步。只有 Adapter 权重不足以证明接续了原优化器和调度状态,数据改变则应另记为新实验。
本章小结:
- AutoDL 负责模型加载与训练,本机通过 SSH 隧道访问页面并保存备份。环境准备要检查当前 Python 和 GPU 是否能实际执行所需计算。
- 数据登记把名称、文件与消息字段连接起来,预览用于核对实际输入。独立验证集通过 eval_dataset 指定,val_size 保持 0;测试集留给后续评估。
- 模型、训练阶段、微调方式和对话模板是不同设置。WebUI、YAML 与启动日志应相符,同一轮选择一种启动方式,训练期间结合进程和日志观察进度。
- 训练完成要由正常结束日志、状态和输出文件共同确认;检查点选择还需结合验证表现。页面断开、训练退出和模型效果不好,应分别定位。
- 训练结果包含 Adapter、配置、日志与检查点,下载后还要核验备份。完整续训比推理加载需要更多训练状态,实验记录则帮助后续复现、评估与交接。
建议下一步: 完成第 9.5 节实验记录,确认结果与备份可用,再进入第 32 章。先用验证输入检查加载和对话,再比较固定测试集上的回答;暂时没有运行条件时,可用下一章附带预测练习评分,并注明材料来自课程案例。