AI 驱动全栈开发新范式 & AI Agent 开发 & 大模型应用开发 & AI Native 软件工程 & AI 算法 & 现代架构与业务领域
AI 驱动全栈开发新范式 & AI Agent 开发 & 大模型应用开发 & AI Native 软件工程 & AI 算法 & 现代架构与业务领域
- 前言
- 1.AI Agent 开发 & 大模型应用开发
- 2.AI 算法
-
- 2.1. 传统AI算法
- 2.2 Transformer
- 2.3 Post-Training
-
- 2.3.1 Fine-tuning
- 2.3.2 Reinforcement Learning
- 2.3.3 Fine-tuning V.S. RL
- 2.3.4 Encode text efficiently - Token (Tokenizer & Embedding)
- 2.3.5 模型预测下个Token (From probabilities to next token id)
- 2.3.6 Epoch / Iterations / Batch / Padding / tensor / LLMs GPU并行计算
- 2.3.7 math: Loss, gradients, weight updates & Train
- 2.3.8 Parameter efficient fine-tuning (PEFT, eg. LoRA)
- 2.3.9 RL: Reward and preference, Value Function/Model
- 2.3.10 RL: Training objective and RLHF
- 2.3.11 RL: PPO and GRPO Algorithms, and DPO Algorithms
- 2.3. LLM训练过程 vs 我的语音增强深度学习模型 (主要步骤概括)
- 3.AI 驱动全栈开发新范式 & AI Native 软件工程
- 4.架构与业务(架构与垂直领域)
前言
AI一定会改变开发模式甚至行业岗位
1.AI Agent 开发 & 大模型应用开发
工程方向
1.1. AI Developer Studio/Playground
开发平台/工具一般包含多种先进的LLM,并开放了很多定制化设置,供用户开发。相比于纯ChatGPT这种使用工具,AI Developer Studio是基于AI的工作平台。
一般来说,满血的账号费用都较高,免费使用的阉割了很多功能,仅开放一小部分数据训练、参数调整以及少量LLM供选择。
1.Vertex AI (Geogle)
-
Vertex AI Studio:Multimodal、Language(text/tasks/code…)、Vision(video/image…)、Speech(audio…)
-
LLM (Large Language Model) prompt: Zero-shot prompting / One-shot prompting / Few-shot prompting
-
Prompt design:
1.freedom:大模型直接问答
2.structured(Context给出背景,Example给出想要的input和output,以规训重点,最后Test):大模型根据你的偏好,save一个特定大模型,回答你的问题 -
How to design Prompt(Prompt工程师):
Be concise
Specific and clearly-defined
ask one task at once
ask to classify instead of generate(要选择题最好不要发散式)
include examples -
temperature(概率分布): 对回答的随机程度进行调优, 0-1, 0是确定, 1是随机, 即越靠近0概率越大
top K: 对回答的概率最高的一部分答案进行随机
top P: 对回答的概率最高的答案进行累加,直到累加值达到top P,即对回答的概率最高的答案进行随机 -
模型调优(Model tuning):
How to customize and tune a gen AI model:
越来越偏技术:
1.Prompt design: Input(required) Context(optional) Example(optional)
2.Adapter tuning(Parameters-efficient tuning): Fine-tuning a model on a specific task or dataset.监督,给出一小部分样本调优参数
3.Reinforcement tuning(Parameters-efficient tuning): Fine-tuning a model on a specific task or dataset.非监督调优
4.Distilling(Student model weight updates): 在Geogle cloud可以做, 可以更加生成特定模型。我理解就是迁移学习。 -
Difference between AI and Machine Learning:
输出确定的、“旧的”东西,用学术的话,输出的是Number、Discrete、Class、Probability的,叫ML。
能输出“新的”东西,用学术的话,输出的是Natural language、Image、Audio(即所谓的非结构化数据),叫GEN AI。
举个例子,如果输入猫狗图片,且每张图片label是文字“猫”、文字“狗”。训练后模型最后能预测猫狗,这叫ML。
如果输入大量的猫狗图片,且按照你想要的方式进行训练(这里涉及到模型了,具体需不需要lable或者怎么lable有疑惑)。训练后模型最后能生成新的猫狗图片,这叫Gen AI。 -
AI Develop
Data Security,开源的模型,私有化部署local AI(teacher student…)
2.Azure AI Foundry (Microsoft)
1.2. AI Chat 开发
1.并发编程支持
2.对话类通信协议要求
WebSocket在实时双向通信和流式数据传输的AI聊天应用中确实是最佳实践。WebSocket是一种网络协议,与HTTP协议处于同一层级,都是应用层协议,它们都建立在TCP传输层协议之上。
除了WebSocket之外,还有几种类似的实时通信技术,它们各有特点和适用场景:
1.Server-Sent Events (SSE) :
- 基于HTTP协议的单向通信技术,服务器可以主动向客户端推送数据 1
- 与WebSocket的主要区别:
- SSE是单向通信(仅服务端→客户端),而WebSocket是双向全双工通信 4
- SSE基于HTTP协议,使用简单且内置自动重连机制 1
- WebSocket需要独立协议(ws://或wss://),相对复杂 4
2.MQTT (Message Queuing Telemetry Transport) :
- 轻量级的发布/订阅消息协议,专为低带宽、高延迟网络设计 2
- 特点:
- 采用发布/订阅模型,解耦消息生产者和消费者 2
- 适用于物联网(IoT)场景,如传感器数据传输 2
3.HTTP Streaming :
- 通过HTTP长连接实现服务器向客户端持续推送数据 3
- 与WebSocket的区别:
- HTTP Streaming复用HTTP连接,开销较低 4
- WebSocket需要独立TCP连接,开销较高 4
这些技术的选择取决于具体的应用场景:
- 如果只需要服务器向客户端推送数据(如实时通知、股票行情),SSE可能是更好的选择 4
- 如果需要双向实时通信(如聊天应用、在线游戏),WebSocket更合适 2
- 对于物联网设备通信,MQTT因其轻量级特性而被广泛采用
前端 - 后端 - MCP 典型流转:
前端 <--WebSocket--> 后端(Agent) <--MCP(SSE)--> 搜索工具server
<--MCP(HTTP)--> 其他简单工具server
<--MCP(WebSocket)--> 需要双向通信的复杂工具server
<--MCP(stdio)--> 本地工具server
1.3. RAG 开发
-
RAG:Retrieval-Augmented Generation的缩写,指“检索增强生成”,这是一个跨越检索和生成任务的框架,通过先从数据库或文档集合中检索到相关信息,然后利用生成模型(如Transformer模型)来生成最终的输出。工程领域。
-
LLM应用方案。将大模型应用于实际业务场景遇到几个问题:1.无法具备全部知识(包括实时数据、非公开数据、私有数据、私有专家知识等) 2.幻觉(无法具备全部知识)3.数据安全 4.微调计算成本较高
-
核心思想:检索 增强 生成。检索体现在:私有数据/数据存储到向量数据库(因为结合了transformer训练时数据处理word embedding)后高效的检索能力,召回目标知识。生成体现在:大模型+Prompt工程,将召回的知识合理利用,融入到Prompt里,大模型更根据目前的提问+能够参考相应的知识(上下文),生成目标答案。主要分三个阶段:数据准备、数据召回(向量搜索出最相近的k个数据库)和答案生成
-
向量数据库:Faiss等等
-
词嵌入Embedding:将高维、稀疏的离散数据,如文本、图像、用户行为等,映射到低维、稠密的连续向量空间。通过向量形式能够精准捕捉数据间的潜在语义关系,使得计算机可以通过几何距离,如欧氏距离、余弦相似度,来量化分析数据的相似性和相关性。
-
提示词工程(Prompt Engineering)
Tips:
Prompt工程:一般包括任务描述、背景知识(检索得到)、任务指令(一般是用户提问)
传统朴素RAG流程:
Document -> Chunking - > Embedding -> VectorStore <- Embedding <- Query/Prompt(LLM) <- Query
↓
Retrieval chunks(高维向量语义相似度, VectorDB针对向量快速检索/大规模并行处理/存储等专业功能)
↓
Prompt/Answer
↓
LLM
↓
Answer to User
RAG 2.0 针对不足,进一步在流程编排和工程/算法上做优化:
RAGFlow框架
1.4. AI Agent 开发
1.4.1 基本介绍
-
能够基于任务目标执行(行动,动作,action)多步骤动作的系统,它能通过调用工具、API 、知识库、插件、代码库等等,动态规划并完成复杂任务。Agent 具备一定程度的自主决策能力。
-
核心组成:
说法一:
Brain: 处理/计算/记忆/计划/决策
Perception: 多模态输入
Action: 调用工具、SDK、API 、知识库、插件、代码库等等,完成交互
说法二:
Planning, Memory, Tools, Action (22年, Reasoning (简单的Planning) + Action = ReAct)
-
开发方式
- 企业级Cloud NoCode/LowCode AI Agent开发: 阿里云百炼(发布的Agent也可以通过暴露API的方式引到自己的应用里), Azure AI Foundry
- 在线或本地(因为开源)NoCode/LowCode AI Agent开发: Coze(个人开发者 & Demo应用), Dify(企业级)
- 代码级别AI Agent开发框架:RAGFlow, LangChain/LangGraph
-
Agent 设计模式
Reflection pattern:LLM审核LLM(LLM串)
Tool use pattern:Tool, Database, API and **MCP**
==ReAct pattern:Reasoning and Acting. 由于推理类模型发展,由R模型自己出调用方式及顺序。再串通用模型(G模型)。最基础。该范式有论文理论支撑==
Planning pattern:升级成任务维度。每个任务交给一个ReAct Agent。
Multi-agent pattern:升级成人类组织维度。每个工作交给特定职位Agent,且能内部之间交互。
Reflection Pattern: 仅大模型,无tool,
Tool Use Pattern: 大模型 + tool.
ReAct Pattern: Reason大模型 + tool + 大模型.
Planning Pattern: 和上面一样,但强调了Plan这个动作.
Multi-Agent Pattern: 和上面一样,但强调了多Agent协作.
ReAct典型: Reason and Act(Thought, Action, Observation. Loop and Finish)。先做,然后再根据做的结果看下一步。最基础。该范式有论文理论支撑。
Plan典型:Prompt工程。先Plan,然后再按照计划执行每一步。
(最佳实践中Plan 可以+ ReAct)
Multi Agent典型:每一个unit领域都是一个Agent。并结合起来。MoE的multi Agent
- Agent形态分类范式
1.基于场景的Agent分类:Action Agent (NL2SQL), Simulation Agents (角色,斯坦福虚拟小镇), Auto-Agent (Auto-GPT, 很多很多的Prompt工程技巧)
2.基于技术的Agent分类:chatbot, planning, multi-agent, reflection, RAG, evaluation
其中:
RAG: Corrective RAG, CRAG. 对检索文档反思、评分。召回时高频知识别太多,低频知识别没有的场景,充分挖掘知识库,通过阈值设置、topk等等。
planning: LLMCompiler, 将任务分解为多个子任务,并形成有向无环图DAG排出任务执行顺序,并设置权重
reflection: Language Agent Tree Search, LATS. Tree of Thoughts?
multi-agent: 分治. 大模型底层训练有MoE,Agent也可以MoE. 这些Agent的调度用Router/Supervisor来控制
3.基于智能的Agent分类:模型嵌入
1.4.2 12-Factor Agents - Principles for building reliable LLM applications
https://github.com/humanlayer/12-factor-agents
- How We Got Here: A Brief History of Software
Even if LLMs continue to get exponentially more powerful, there will be core engineering techniques that make LLM-powered software more reliable, more scalable, and easier to maintain.
Instead of software engineers coding each step and edge case, you can give the agent a goal and a set of transitions. And let the LLM make decisions in real time to figure out the path.
给出了确定性代码->热部署/配置性代码->AI驱动的演进
Agents as loops
Put another way, youve got this loop consisting of 3 steps:
1.LLM determines the next step in the workflow, outputting structured json ("tool calling")
2.Deterministic code executes the tool call
3.The result is appended to the context window
4.repeat until the next step is determined to be "done"
initial_event = {"message": "..."}
context = [initial_event]
while True:
next_step = await llm.determine_next_step(context)
context.append(next_step)
if (next_step.intent === "done"):
return next_step.final_answer
result = await execute_step(next_step)
context.append(result)
Our initial context is just the starting event (maybe a user message, maybe a cron fired, maybe a webhook, etc), and we ask the llm to choose the next step (tool) or to determine that we're done.
Micro agents: Human - Deterministic Code - Agent
- Natural Language to Tool Calls
将人类语言翻译为一个固定格式的JSON数据结构,让大模型更容易识别意图
结合了大型语言模型的语言理解能力和传统编程的精确执行能力,让AI能够将我们的模糊指令转化为具体的、可操作的计算机任务
我理解这里应该不是说MCP,应该可能是说通过某种方式,提前将NL2JSON,给到大模型
我觉得这里应该说的是意图识别(Intent Detection),或意图识别和槽位抽取(Slot Filling)
1.1. 意图识别(https://developer.aliyun.com/article/1675940)
意图识别和槽位抽取是自然语言理解(NLU)的两个关键部分,会直接影响智能体的交互质量和用户体验。
意图识别(Intent Detection)的核心作用在于准确判断用户的语义目的。系统能将用户输入映射到预定义的意图类别(如"查询天气"、“预订餐厅”),这一步骤决定了后续业务流程的正确走向。若意图识别错误,整个对话流程就会偏离用户真实需求。
槽位抽取(Slot Filling)则负责结构化关键信息。从语句中提取出时间、地点、数量等实体参数。这些槽位值构成了执行具体操作的必备参数。例如在订餐场景中,必须准确提取"菜品名称"、"送餐地址"等核心槽位。
解决方案:初级方案(提示词工程)。后面提出了很多种方案,比如结合RAG结合单独LLM做意图识别和槽位抽取,等等复杂的工程处理方式。
- Own your prompts
Remember: Your prompts are the primary interface between your application logic and the LLM.
Having full control over your prompts gives you the flexibility and prompt control you need for production-grade agents.
重视Prompt Engineering,可以借助专门的prompt engineering优化工具,或者大模型来不断优化。并做A/B Test.
2.1. CoT (Chain of Thoughts)
最初是由Google Research在2022年发表的论文《Chain-of-Thought Prompting Elicits Reasoning in Large Language Models》中正式提出的概念。
最经典最基本最简单的,在Prompt里加一句: Let’s think step by step
- Own your context window
Everything is context engineering. LLMs are stateless functions (LLM是无状态) that turn inputs into outputs. To get the best outputs, you need to give them the best inputs. The context window is your primary interface with the LLM. 这个context window也是用JSON结构(或XML-style, 或这种半结构化方式)去表达的,有Standard和Custom Context Formats.
Creating great context means:
- (提示词工程)The prompt and instructions you give to the model
- (RAG)Any documents or external data you retrieve (e.g. RAG)
- (状态/历史动作维护,侧重当前任务的交互过程和结果)Any past state, tool calls, results, or other history
- (记忆, 范围更大)Any past messages or events from related but separate histories/conversations (Memory)
- (结构化输出)Instructions about what sorts of structured data to output
这里的上下文方式不包含:超参数如TopKTemperature, 微调模型。
Tips: 传统深度学习的超参数在训练和模型结构上,比如learning rate, batch size, epochs, 激活函数…等等。大模型这些超参数,指的是大型语言模型在文本生成(推理/Inference)阶段使用的超参数。它们不改变模型已经学到的权重,而是控制模型在生成文本时如何从其内部学到的概率分布中进行采样。
共同点:
两者都是外部参数,由用户或开发者设置,而不是模型通过训练学习。
两者都对模型的最终输出或性能产生显著影响。
主要区别:
作用阶段:
传统深度学习超参数主要作用于训练阶段,影响模型的学习过程和最终权重。
LLM 生成超参数主要作用于推理/生成阶段,影响模型如何利用其已学到的知识来生成文本。
控制目标:
传统深度学习超参数控制模型的学习能力、结构和泛化能力。
LLM 生成超参数控制生成文本的特性,如创造性、多样性、重复性等。
- Tools are just structured outputs
Tools don’t need to be complex. At their core, they’re just structured output from your LLM that triggers deterministic code. This creates a clean separation between the LLM’s decision-making and your application’s actions. The LLM decides what to do, but your code controls how it’s done.
MCP
- Unify execution state and business state
Execution state: current step, next step, waiting status, retry counts, etc.
这指的是关于流程如何运行的信息。
它包括当前步骤、下一步、等待状态、重试次数等,通常是流程控制和管理所需的元数据。
想象一下一个任务管理器,它记录了任务是“进行中”、“待处理”还是“已失败”。
Business state: What’s happened in the agent workflow so far (e.g. list of OpenAI messages, list of tool calls and results, etc.)
这指的是关于工作流中实际发生了什么的信息。
它包括所有与业务逻辑相关的数据,例如OpenAI消息列表、工具调用及其结果列表等。
想象一下一个聊天记录,它包含了所有用户和AI之间的对话内容。
- Launch/Pause/Resume with simple APIs
Agents are just programs, 应具备像普通程序一样的生命周期管理能力,即能够通过简单、标准化的API进行启动 (Launch)、暂停 (Pause) 和 恢复 (Resume), 期望它具备基本的生命周期操作
- Contact humans with tool calls
全部使用JSON(或类似的结构化格式)是实现通过工具调用联系人类的关键技术手段。
- Own your control flow
人类参与过程,但不阻塞整个Agent,即异步等待人类决定/参数,并具备恢复能力。
- Compact Errors into Context Window
将错误(一般是执行某个工具时发生的)添加到上下文窗口,限制连续错误并升级处理,
- Small, Focused Agents
Rather than building monolithic agents that try to do everything, build small, focused agents that do one thing well. Agents are just one building block in a larger, mostly deterministic system.
即 Multi-Agent
同时给出了观点:就算LLM更智能,能处理的更多,这样分块也是有意义的
- Trigger from anywhere, meet users where they are
未来
-
Make your agent a stateless reducer
-
pre-fetch all the context you might need
如果你的AI代理(LLM)有很高的概率会需要某些特定的信息来做出决策或执行任务,那么就不要让LLM自己去请求这些信息(这会浪费LLM的调用轮次和token)。相反,你应该在调用LLM之前,就以确定性的方式预先获取这些信息,并将其直接注入到LLM的上下文窗口中。
例:部署任务,几乎总是需要git标签,那么LLM会先选择 list_git_tags,然后系统执行,再将结果返回给LLM,LLM才能选择 deploy_backend_to_prod。这至少需要两轮LLM调用。
1.4.3 Prompt Engineering
- zero-shot, one-shot, few-shot
- A/B Test:来回试验
- 一些案例
角色:减少二义性,让“通用”瞬间变得“专业”。
指示:对具体任务进行详细描述。
上下文:给出与任务相关的其它背景信息(如历史对话、情境等)。
例子:是大模型生成输出时的一个重要参考,对输出结果有很大帮助。
输入:任务的输入信息,最好在提示词中有明确的“输入”标识。
输出:输出的格式描述,比如输出不超过十个字、以JSON格式返回结果等。
Magic Prompt:
Let's think step by step.(CoT)
Read the question again.
Thanks to https://mp.weixin.qq.com/s/DODEfYvyMAXTZS-edtXeMg
1.4.4 LLMs - Ollama / Hugging face
- 拉开源大模型. Get up and running with large language models.
- 本地部署(非常方便的)多种LLMs
- 支持模型微调
- 支持命令行运行与直接交互、Ollama 的 Python SDK使用、API交互、WebUI
1.4.5 MCP
MCP, Model Context Protocol,旨在统一大型语言模型(LLM)与外部数据源和工具之间的通信协议。分client和server。client在host里。
host指的我们的调用程序本体,可以是LLM程序,IDE,AI工具,LangChain代码框架。
client / server,感觉和之前原始手写socket一个意思,一个client一个server建立网络通信TCP/IP。
协议
- MCP 的核心价值
方面 直接 API 调用 MCP 协议
错误处理 各不相同 统一格式
类型安全 依赖文档 内置 Schema
版本管理 手动处理 自动协商
服务发现 手动配置 自动发现
扩展性 修改代码 动态添加
参数验证 运行时错误 预先验证
AI 集成 需要适配 原生支持
(这里的API是广义抽象的API,包含网络接口、本地工具等等)
为 AI 而生:专门为 AI 模型设计的工具调用协议
自描述能力:服务可以告诉 AI 自己能做什么
类型安全:减少运行时错误
统一接口:一套协议访问所有服务
动态扩展:无需修改代码即可添加新功能
微服务友好:天然支持分布式架构
MCP 让 AI 模型能够像人类一样"学会使用新工具",而不是像传统程序一样"被硬编码使用特定 API"。
- MCP支持的传输协议(transport参数)
stdio (标准输入/输出)
通过进程的标准输入/输出流进行通信
主要用于本地进程间通信
配置中使用 “transport”: “stdio”
HTTP/HTTPS
通过HTTP或HTTPS协议与远程服务器通信
支持REST风格的API调用
配置中使用 “transport”: “http” 或 “transport”: “https”
WebSocket
提供全双工通信通道
适合需要持久连接和实时数据交换的场景
配置中使用 “transport”: “websocket”
Server-Sent Events (SSE)
用于服务器向客户端推送数据的单向通道
适合流式响应和事件通知
配置中使用 “transport”: “sse”
(流式返回,支持大结果分块传输,服务器主动推。可实现搜索结果逐步返回)
Unix Socket
用于同一系统上的进程间通信
比stdio有更好的性能
配置中使用 “transport”: “unix”
TCP Socket
直接通过TCP套接字通信
适合需要自定义协议的场景
配置中使用 “transport”: “tcp”
还在更新,还有什么StreamableHttp
mcp Client和mcp Server部署在同一台机器上,通过标准输入输出通信的方式就是STDIO模式。部署在不同机器通过Http请求通信就是SSE模式和StreamableHttp模式。STDIO因为在本地运行,是绝对安全的;但SSE和StreamableHttp(比SSE更高可用,网络上那些个问题)模式若暴露连接方式,可能会有安全问题。
- MCP 协议架构采用了分层架构设计
┌─────────────────────────────────────┐
│ Application Layer │ ← 工具调用、资源访问、提示词
├─────────────────────────────────────┤
│ Protocol Layer │ ← JSON-RPC 2.0 消息格式
├─────────────────────────────────────┤
│ Transport Layer │ ← stdio, HTTP, WebSocket, SSE
└─────────────────────────────────────┘
Tips: HTTP是一问一答,连接即用即关;SSE是长连接,支持服务器主动推送
- The Agent Protocol Stack
除了MCP外的新名词(协议级别)
A2A: Agent to Agent
AG-UI: Agent to User Interface
1.5. LangChain & LangGraph
LangChain for LLM Application Development:
- LangChain is a open-source framework for developing applications powered by language models(LLM Applications).
- python version(packages) & JS/TS version(packages)
- Composition and modularity
- Components / Off-the-shelf chains: Off-the-shelf chains make it easy to get started. Components make it easy to customize existing chains and build new ones.
modules:
- Components fall into the following modules:
1.Models
2.prompt and output parsers
3.index(for RAG)
4.Chain(eg. prompt + LLM + output parsing. And more and more application chains)
5.Agents(LLM base, Memory(chatbot), Action)
Models, Prompts, Output Parsers: API/Local. (LangChain’s hello world)
Memory: LLMs are stateless. Chatbots appear to have memory by providing the full conversation as context. However, LLMs API is charge by token. Langchain provide some memory tricks, feature like keep just a number of conversiation/tokens.
Chains: use ‘MoE’ idea in Enginner. Intertesting.
Q&A with Documents: word embedding, vector database, index. query come, after this, give LLM. Stuff method, simple RAG. Additional 3 methods: Map_reduce/Refine/Map_rerank
Evaluation: use LLM to evaluation answer.
Agent: pre/cus tools.
- LCEL
LangChain Expression Language (LCEL): 异步、批处理和流支持
eg. 代码里写chain = prompt | model | output_parser,类似学linux操作系统里的linux命令的piple概念。
- ReAct
The ReAct (Reasoning and Acting) pattern is a framework for building AI agents that combine reasoning (thinking) with acting (taking actions).Break Down.
Built an agent from scratch.
Prompts: can import. allow reusable prompts(LangChain hub).
Tools: get a tool from the library.
Others: pattern is also like graphs.
- LangGraph
Knowledge Graph 知识图谱:是结构化的语义知识库,用于迅速描述物理世界中的概念及其相互关系。实体,关系,实体。
图数据库:Neo4j
LangGraph 是一个结合 LangChain 与知识图谱(Knowledge Graph)的应用,旨在通过结构化的知识库增强语言模型的理解和响应能力。
Graphs to build Agent:
1.Nodes(Agents or functions), Edges(connect nodes), Conditional edges(decisions)
2.Data/State: State Memory (State Snapshot)
3.Image: can draw the image of your graph.
4.powerfull Tool: Search Tool(like Tavily)
5.persistence and streaming: usefully in long running application.
checkpoints(database, SQL, NoSQL) for persistence.
get individual messages / observation message / token of streaming(not .invoke)
Above of these can config as 2 thread to separate 2 mode. (another model don’t have this model’s persistence/memory)
6.user/human can interrupt the loop and choose(improve)
- 源码级技术细节
LangChain/LangGraph 中常见的执行方法:invoke, stream, stream_events,以及它们对应的异步版本 ainvoke, astream, astream_events。
-
invoke() 和 ainvoke():一次性执行,返回最终结果
同步版本: invoke(input)
异步版本: ainvoke(input)
行为: 启动 Agent 或 Runnable 的执行,并等待其完全完成。一旦执行完毕,它会一次性返回最终的输出结果。
特点:
非流式: 在执行过程中不会产生任何中间输出。
阻塞/等待: invoke 会阻塞当前线程直到结果返回;ainvoke 会等待结果返回,但不会阻塞事件循环。
简单: 适用于只需要最终结果的场景,代码逻辑最简单。
何时使用: 当你不需要实时反馈,只关心最终答案时。例如,一个后台任务,或者一个简单的问答机器人,不需要展示思考过程。
驱动方式: 你直接调用它,它会自己完成所有工作并返回结果。不需要循环。 -
stream() 和 astream():流式文本输出
同步版本: stream(input)
异步版本: astream(input)
行为: 启动 Agent 或 Runnable 的执行,并返回一个迭代器(同步)或异步迭代器(异步)。每次迭代会产生一个文本块(或包含文本的 AIMessageChunk 等)。它主要关注 LLM 生成的最终文本输出。
特点:
流式文本: 逐字逐句或逐块地返回文本内容。
不暴露内部事件: 通常不会暴露 Agent 内部的详细执行事件,如工具调用开始/结束、中间步骤等。它更像是一个“纯文本流”。
需要循环驱动: 你需要使用 for chunk in … (同步) 或 async for chunk in … (异步) 来迭代并获取这些文本块。每次迭代都会驱动 Agent/Runnable 执行一部分,直到产生下一个文本块。
何时使用: 当你只需要流式地展示 LLM 的文本输出,而不需要展示 Agent 内部的复杂思考过程(例如工具调用细节)时。这对于构建普通的聊天机器人界面非常有用。
驱动方式: 必须通过 for 或 async for 循环来驱动。 -
stream_events() 和 astream_events():流式事件输出
同步版本: stream_events(input, config, version)
异步版本: astream_events(input, config, version)
行为: 启动 Agent 或 Runnable 的执行,并返回一个迭代器(同步)或异步迭代器(异步)。每次迭代会产生一个事件对象。这些事件对象包含了 Agent 内部执行的详细信息,包括文本块、工具调用开始/结束、链的开始/结束、最终输出等。
特点:
最详细的流式: 提供了对 Agent 内部执行过程最细粒度的洞察。
包含所有中间步骤: 你可以捕获到 Agent 的每一个“思考”和“行动”。
需要循环驱动: 你需要使用 for event in … (同步) 或 async for event in … (异步) 来迭代并获取这些事件。每次迭代都会驱动 Agent/Runnable 执行一部分,直到产生下一个事件。
何时使用: 当你需要构建高度交互式、可观察的 Agent 应用时,例如:
实时展示 Agent 的思考链(“Thought”, “Action”, “Observation”)。
在前端区分并展示工具调用和工具结果。
调试 Agent 的执行流程。
在 Agent 运行时进行自定义的日志记录或监控。
驱动方式: 必须通过 for 或 async for 循环来驱动。 -
Runnable
Runnable 是 LangChain 中一个非常核心的抽象概念。它是一个接口(或者说是一个协议),定义了任何可以被“运行”的东西。
核心思想:任何实现了 Runnable 接口的对象,都具备以下能力(至少是其中一部分):
invoke() / ainvoke(): 运行一次并返回最终结果。
stream() / astream(): 流式返回文本块。
stream_events() / astream_events(): 流式返回所有执行事件。
batch() / abatch(): 批量运行。
with_config(): 运行配置。
bind(): 绑定参数。
pipe(): 链式调用。
统一接口: 无论是一个简单的 LLM(ChatOpenAI、ChatAnthropic)、一个工具(BaseTool)、一个提示模板(ChatPromptTemplate)、一个输出解析器(BaseOutputParser),还是一整个复杂的 Agent 或链,只要它们实现了 Runnable 接口,你就可以用统一的方式(invoke、stream 等)来调用它们。
可组合性: Runnable 使得 LangChain 的各个组件可以像乐高积木一样自由组合。你可以将多个 Runnable 通过 | 运算符(pipe 方法)连接起来,形成更复杂的链。
灵活性: 无论是同步还是异步,无论是单次调用还是流式调用,Runnable 都提供了相应的接口,让开发者可以根据需要选择最合适的执行方式。
典型的,langgraph的create_react_agent 返回的 agent 对象(一个 CompiledGraph 实例)是 LangChain Runnable 接口的一个实现。这意味着你可以对这个 agent 对象调用 invoke()、ainvoke()、astream_events() 等方法,就像你对任何其他 LangChain Runnable 组件一样。
LangChain Runnable 的实现者:
ChatOpenAI (一个 LLM)
Tool (一个工具)
ChatPromptTemplate (一个提示模板)
StrOutputParser (一个输出解析器)
create_react_agent 返回的 CompiledGraph (一个复杂的 Agent)
你的 CustomAPIModel (自定义 LLM)
它们都实现了 Runnable 接口,但内部逻辑和所代表的功能完全不同。
Runnable 的特性正是 LangChain 能够使用 | 运算符(管道操作符)进行链式连接的根本原因。在 LangChain 中,| 运算符被重载(overload)了,它实际上是调用了 Runnable 接口的 pipe() 方法。 -
Agent 与 model
用户请求
|
V
LangGraph (ReAct Agent) (给create_react_agent(debug=true)会打印checkpoint)
|
V
LLM model
|
V
LangGraph (ReAct Agent)
|
+--- IF AIMessage 有 tool_calls:
| - 执行工具 (使用传入的 `tools` 列表)
| - 将工具结果作为 ToolMessage 添加到历史
| - 返回步骤1,再次调用Model (形成循环)
|
+--- IF AIMessage 只有 content:
- 视为最终答案
- 结束循环,返回结果给用户
所以:
是工具还是文本: 主要由 LLM 的推理能力决定,定制Model 负责解析这种决定(如果是langchain支持的model则不需要这个)。
是循环还是结束: 主要由 langgraph (ReAct Agent) 根据 CustomAPIModel 返回的 AIMessage 类型(是否有 tool_calls)来决定。如果 tool_calls 存在,就进入循环;如果不存在,就结束。
- Debugging agents in langchain
给create_react_agent(debug=true)会打印checkpoint
- Multi-Agent: Routing Agent & Agentic
first is check bussiness or small talks, so, it has Intent Detection.
涉及到的一些技术名词,thread_id, invoke, functools.partial(set up the agnet node, but not execute it)
用langgraph的:
router_graph=StateGraph(RouterAgentState),
router_graph.add_node(self) -> self mean router-self
router_graph.add_node(otheragent…)
router_graph.add_conditional_edges(all)
router_graph.add_edge
router_graph.set_entry
- State
1. state:全局共享的通信总线与上下文
在 LangGraph 中,state 并不是某个 Node 独有的“内部状态”,而是整个 LangGraph 工作流的“全局状态”或“当前上下文”。它充当了所有 Node 和 Agent 之间唯一的、统一的通信总线和共享内存。
- 单一事实来源 (Single Source of Truth): state 是一个贯穿整个图执行过程的单一数据结构。它在整个 LangGraph 实例的生命周期中是唯一的,无论执行路径如何,传递给下一个 Node 的都是这个 state 的最新版本。
- 输入与输出机制:
- - 当 LangGraph 路由到任何一个 Node 时,都会将当前的全局 state 作为输入传递给该 Node 函数。
- - Node 函数基于接收到的 state 执行逻辑,并返回一个 dict,其中包含了它希望对全局 state 进行的增量更新。
- 智能合并与累积: LangGraph 框架会智能地将 Node 返回的增量更新合并到当前的全局 state 中。
- - 对于列表类型(例如 messages),新的元素会被追加到列表中。
- - 对于其他非列表类型(例如字符串、数字),新的值会替换旧值。
- - 如果 Node 返回了 state 中不存在的新键,这些新的键值对会被添加到 state 中。
案例:state 的增量更新与合并
假设初始 state 为 {"messages": [], "plan": ""}。
Node 返回 {"messages": [new_message]}: new_message 会被追加到 state["messages"] 列表中。
Node 返回 {"plan": "new_plan_text"}: state["plan"] 会被更新为 "new_plan_text"。
Node 返回 {"tool_output": "..."}: state 会新增一个键值对 {"tool_output": "..."}。
- 持久性与连续性: state 的更新是持久的,一旦某个 Node 更新了 state,这个更新就会保留下来,并对后续所有 Node 可见,从而实现了 Agent 的“记忆”功能。
2. messages 列表 - 对话的连续卷轴与外部模型交互
state 中累积的 messages 列表是所有 Agent Node 叠加起来的完整消息列表,它记录了整个对话和操作的历程。这个列表也是与外部大模型 API 交互的主要载体。
- 持续增长: 从用户初始输入开始,state["messages"] 列表会不断增长。
- Node 贡献:
- - 用户输入会作为 HumanMessage 初始化列表。
- - 每个 Agent Node(或包含 LLM 交互的逻辑)的回复、思考 (AIMessage)、工具调用 (AIMessage with tool_calls) 和工具执行结果 (ToolMessage) 都会被追加到 state["messages"] 列表中。
'''
[
HumanMessage(content="帮我规划一次日本东京的旅行。"),
AIMessage(content="好的,我来帮你规划。首先,你需要告诉我旅行的时间、预算和偏好。"),
HumanMessage(content="我想在明年春天去,预算大概,喜欢逛街和美食。"), # 用户回复
AIMessage(content="明白了。根据您的偏好,我建议您选择3月底到4月初,可以赶上樱花季。我可以为您推荐几个购物区和特色餐厅。"), # 细化Agent的回复
# ... 更多可能的AIMessage或ToolMessage
]
'''
- 完整上下文: 这种累积确保了每个 Agent 都能访问到完整的对话历史,包括用户输入和之前所有 Agent 的输出。这对于理解语境、避免重复提问和进行链式推理至关重要。
- 与外部大模型的交互:
- - ==当 LangGraph 中的某个 Node 需要与外部大模型(如 OpenAI GPT、Anthropic Claude 等)进行交互时,它会从当前的全局 state 中提取 state["messages"] 列表。==
- - 这个 messages 列表会被格式化成符合大模型 API 要求的输入(例如,OpenAI API 的 messages 参数)。
- - 大模型处理后返回的响应(例如,AIMessage)会被追加回 state["messages"] 列表,从而更新全局 state。
'''
state["messages"] = [
HumanMessage(content="查询北京天气"),
AIMessage(content="好的,我来帮你查询。")
]
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o")
response = llm.invoke(current_messages) # 将整个消息列表作为上下文发送
(response 可能是 AIMessage(content="北京今天晴,25度。") 或 AIMessage(content="", tool_calls=[...]))
然后,这个 response 会被追加回 state["messages"],供后续 Node 使用。
'''
- 调试与透明度: 完整的 messages 列表也极大地提高了调试的便利性,可以清晰地看到每个 Agent 在何时、基于何种信息做出了何种响应。
3. react_agent 在 LangGraph 中的构成与协作
LangChain 提供的 react_agent(或类似 create_tool_calling_agent)本身是一个预构建的 Agent 逻辑,它不是 LangGraph 中的一个单一 Node。当将其集成到 LangGraph 时,它的内部运行机制会映射到 LangGraph 的多个 Node 上,或作为一个子图运行。
- react_agent 的内部构成: 一个典型的 ReAct Agent 在 LangGraph 的内部结构中,至少会涉及以下逻辑单元:
- - LLM 决策 Node (Agent Node): 负责 LLM 的思考、工具调用决策或生成最终答案。它会从 state["messages"] 中获取上下文,并将大模型的响应(AIMessage)追加回 state。
- - 工具执行 Node (Tool Node): 负责接收 LLM 决策 Node 提供的工具调用信息,然后实际执行工具函数。工具执行结果 (ToolMessage) 会被追加回 state["messages"]。
- - 路由/循环决策 Node (Router Node): 通过条件边 (conditional_edge) 实现,根据 LLM 的输出(例如 state["messages"] 中最新的 AIMessage 是否包含 tool_calls)决定是继续循环(执行工具或再次思考)还是结束。
- 两个 react_agent 的结合:
- - 当两个独立的 react_agent (例如 react_agent_A 和 react_agent_B) 在同一个 LangGraph 中协作时,它们通常会被封装成顶层图中的独立 Node 或子图。
- - 例如,Node A 封装 react_agent_A 的整个 ReAct 循环,Node B 封装 react_agent_B 的整个 ReAct 循环。
- - 一个或多个路由 Node 会根据当前的 state 决定何时从 Node A 切换到 Node B,以及何时结束整个图。
- - 从顶层图的视角看,可能只有少数几个主要 Node,但每个 Node 内部又包含了其 ReAct 循环的逻辑单元。
4. state 在复杂场景下的存储与累积(ReAct 内部循环与多 Agent 切换)
核心观点: 无论是一个 react_agent 内部的循环,还是两个 react_agent 之间的切换,state 始终是同一个、全局的、不断累积和更新的共享上下文。
总结来说,LangGraph 的 state 机制是其实现复杂 Agent 编排的关键。它提供了一个统一的、动态的、可累积的上下文,使得不同的 Agent 或节点能够无缝地共享信息、协作完成任务,并具备完整的“记忆”能力。同时,state 中的 messages 列表是 LangGraph 与外部大模型 API 进行交互的核心桥梁,确保了上下文的完整性和连贯性。
- SQLite
一款轻型-轻量级的数据库,是遵守ACID的关系型数据库管理系统。
架构:MySQL 是一个客户端/服务器架构的数据库管理系统,这意味着它需要一个单独的服务器进程来运行。而 SQLite 是一个无服务器的数据库管理系统,它直接读取和写入磁盘文件。
存储:MySQL 数据库存储在磁盘上的文件中,但需要通过 MySQL 服务器进程来访问和管理。SQLite 数据库也存储在磁盘上的文件中,但可以直接通过 SQLite 命令行工具或编程语言的 SQLite 库来访问和管理。
SQLite是文件级的。所以替代的是文件存储的级别,不是client/server数据库的级别。单体/接单用足够。直接给它存成文件。
LangGraph写Agent用到了。
- 根据API自定义包装LangChain支持模型
graph TD
A[用户输入] --> B{模型类型}
B -->|OpenAI/Claude| C[LangChain直接传递tools]
C --> D[API原生处理工具]
D --> E[返回结构化tool_calls]
E --> F[LangChain自动执行工具]
F --> G[获得最终结果]
B -->|自定义模型| H[LangChain包装tools到提示词]
H --> I[API接收纯文本]
I --> J[模型生成文本形式的工具调用]
J --> K[手动解析工具调用]
K --> L[手动执行工具]
L --> M[构造新提示包含结果]
M --> N[再次调用API]
N --> G
(OpenAI在2023年推出Function Calling功能,Claude也随后支持了Tool Use功能.这些都是API级别的原生功能)
-
Agent生产部署:LangServe / LangSmith
-
Spring AI
Java有Spring AI
1.6. Agentic AI
Agentic AI 是一种新兴的智能架构,其利用多个专业化 Agent 协作来实现复杂且高层次的目标。这些系统由模块化的 Agent 组成,每个 Agent 负责更广泛目标的一个独特子组件,并通过集中式协调器或去中心化协议进行协调。这种结构标志着从单一 Agent 架构中通常观察到的原子化、响应式行为的概念转变,转向为一种以动态 Agent 间协作为特征的系统级智能形式。
5 patterns in Agentic AI
- Planning: Decide on a course of action or break down a complex task into smaller tasks.
- Tool use: Choose available tools, decide on their inputs, and collect outputs.
- Routing: Route a request to alternate destinations by analyzing the input.
- Reflection: Review outputs or decisions, provide feedback on improvements, and validate accuracy.
- Multi-agent: Multiple independent AI agents work together to accomplish a complex task.
2.AI 算法
2.1. 传统AI算法
SCI-2区:LSTM-convolutional-BLSTM encoder-decoder network for minimum mean-square error approach to speech enhancement
EI / 中文核心:基于抛物面焦点麦克风预处理和迁移学习的语音增强方法
专利:一种基于混合掩蔽学习目标的语音增强方法
2.2 Transformer
1.绝大多数先进的多模态大模型(如 GPT-4o、DeepSeek、Qwen2.5 等)的底层架构均基于 Transformer,但会根据多模态需求进行扩展和优化。Transformer 的核心优势在于其 自注意力机制(Self-Attention),能够高效建模长距离依赖关系,并天然支持并行计算。对于多模态任务(文本、图像、音频等),模型会通过以下方式扩展:
- 多模态输入编码:将不同模态数据(如文本、图像、音频)统一编码为向量序列。例如:图像被切分为小块并通过视觉编码器(如 ViT)转换为嵌入向量;音频通过语谱图、波形编码、短时傅里叶变换等预处理方式,预处理为向量序列(my work)。
- 跨模态注意力机制:允许不同模态的嵌入向量在注意力层中交互。
2.Transformer 模型的训练方式
核心训练范式:自监督预训练 + 有监督微调
- 阶段 1:预训练(自监督学习):让模型从海量无标注数据中学习通用表示(如语言规律、跨模态关联)。输入和标签均来自同一数据,无需人工标注。典型的方法有掩码语言建模、自回归预测、多模态对齐等
- 阶段 2:微调(有监督学习):针对特定任务(如问答、图像描述生成)优化模型。需要人工标注的输入-输出对。指令微调、强化学习等
- 阶段 3:推理(部署后)。训练时需标签(显式或隐式),推理时仅需输入。
3.Transformer & LLM & Attention是什么?
大型语言模型,特别是 Decoder-only 架构(如 GPT 系列),之所以能够进行下一个词元预测,并展现出惊人的语言理解和生成能力,其核心在于它们能够深度理解上下文,并高效地利用这些理解。这主要归功于以下几个关键机制:QKV 操作、注意力机制、多头注意力以及因果掩码。
1. QKV 操作与词嵌入维度:上下文理解的基石
QKV 操作是 Transformer 模型中注意力机制的基础,它负责为模型构建丰富、动态的上下文表示。在 Transformer 模型中,每个输入的词元(或子词)首先会被转换成一个固定维度的向量,这被称为词嵌入(Word Embedding)。这个向量的维度通常被称为 d_model(例如 768, 1024, 甚至更大),它代表了模型对词元语义信息的初始理解。
Query (Q): 代表当前词元“我需要什么信息?”它是一个“查询”向量,用于寻找相关信息。
Key (K): 代表序列中所有词元“我能提供什么信息?”它是一个“标签”或“索引”,概括了词元的内容。
Value (V): 代表序列中所有词元“我的实际信息内容是什么?”它包含了词元的具体语义信息。
这些 Q、K、V 向量都是通过将原始词嵌入(维度为 d_model)分别乘以三个不同的线性投影矩阵 WQ,WK,WV得到的。这意味着 Q、K、V 向量的维度也通常是 d_model(或在多头注意力中是 d_k)。
工作原理:
相关性计算: 当前词元的 Q 向量会与序列中所有词元的 K 向量进行点积运算,计算出它们之间的相似度或相关性。
注意力权重: 这些相似度分数经过缩放(除以和 Softmax 函数处理后,转化为注意力权重。这些权重表明了当前词元应该“关注”序列中其他哪些词元,以及关注的强度。
信息提取: 最后,这些注意力权重被用来对所有词元的 V 向量进行加权求和。结果是一个新的向量,它融合了当前词元自身的信息以及它从序列中“关注”到的其他相关信息。
核心意义: QKV 机制使得模型能够动态地、有选择性地从输入序列中提取最相关的信息,从而为每个词元生成一个高度语境化的表示。
2. 因果掩码(Causal Masking):Decoder-only 模型的预测约束
在 Decoder-only LLM 中,QKV 操作有一个关键的限制:因果掩码(Causal Masking)。这意味着当模型处理序列中的某个词元时,它的 Q 向量只能与它自己以及它之前所有词元的 K 向量进行交互。它无法“看到”或“关注”它之后(即尚未生成)的任何词元。这通过在注意力权重计算时,将未来词元对应的注意力分数设置为负无穷来实现,使其在 Softmax 后权重变为零。
模型如何预测下一个词元
模型不是通过“看到”下一个词元的 K 来预测它。相反,它通过 QKV 机制深度理解当前输入序列中最后一个词元的上下文表示。这个高度语境化的向量(经过多层 Transformer 处理后)被送入一个最终线性层(分类器),该分类器根据在训练中学习到的模式,预测词汇表中哪个词元最有可能作为下一个词。这个过程严格遵守因果关系,确保模型像人类一样一步步生成文本。被预测出的词元的 K 向量,只有在它被实际生成并添加到输入序列之后,才会在下一个预测步骤中被其他词元的 Q 向量所“看到”和利用。
3. 注意力机制与多头注意力:多维度理解上下文
注意力机制: 这是一个更广泛的概念,指模型在处理序列元素时,能够动态地、有选择性地关注序列中其他相关元素并提取信息的方法。QKV 流程是实现注意力机制的一种具体方式(即单头注意力)。
多头注意力(Multi-Head Attention): 是对单头注意力机制的重要扩展和优化。它不是运行一个注意力头,而是并行地运行多个独立的注意力头,并将它们的输出拼接起来,再进行一次线性变换。
包含关系: 自注意力是注意力机制的一种。 所有的自注意力都是注意力,但不是所有的注意力都是自注意力。
来源不同:
自注意力: Q、K、V 都来源于同一个序列。例如,在 Transformer 的 Encoder 层中,每个词元都通过自注意力来理解它与输入句子中所有其他词元的关系。在 Decoder 层中,生成中的词元也通过自注意力来理解它与已生成词元的关系。
交叉注意力(Cross-Attention): 这是注意力机制的另一种常见形式,在原始 Transformer 模型的 Decoder 中有使用。在这种情况下,Query (Q) 来自一个序列(例如 Decoder 的输出),而 Key (K) 和 Value (V) 来自另一个序列(例如 Encoder 的输出)。它的作用是让 Decoder 在生成输出时,能够关注到 Encoder 编码的输入序列中的相关信息。
在 Transformer 架构中,我们通常谈论的是自注意力。它允许模型在处理一个词元时,能够同时考虑该词元在整个输入序列中的上下文信息。而“注意力机制”则是一个更宏大的框架,自注意力是其最成功的实现之一。
4.在多轮对话任务中,Attention 的局限性体现在哪些方面
大型语言模型,尤其是基于 Transformer 架构的模型,在进行下一个词元预测时,其核心机制是自注意力(Self-Attention)。自注意力允许模型在处理当前词元(Query)时,回顾并权衡输入序列中所有其他词元(Key 和 Value)的重要性。
会有自注意力的计算成本O(N^2) 问题:每个 Query 向量都需要与序列中的所有 Key 向量进行点积运算,以计算当前 Query 对每个 Key 的相关性。
在 Transformer 的解码器(Decoder)部分,特别是在进行生成任务(如机器翻译、文本生成)时,我们使用的是掩码自注意力。在这种情况下:
为了防止模型在生成当前词元时“偷看”到它还未生成的未来词元,我们会对注意力分数矩阵应用一个掩码。
这个掩码会强制让每个 Query 只能关注它自己以及它前面的词元。对于未来词元的位置,注意力分数会被设置为一个非常小的负数,这样在 Softmax 之后,这些位置的权重就几乎为零。
对于序列中的第一个词元,它的 Query 确实只会与它自己计算点积(因为它前面没有其他词元)。
即使在掩码自注意力的情况下,为什么我们仍然说它是 On2 呢?
潜在的交互仍然是 N×N。 尽管我们应用了掩码,但在计算 Query 和 Key 的点积时,这会得到一个 N×N 的注意力分数矩阵。
掩码操作是在这个 N×N 矩阵上进行的。 也就是说,我们首先计算了所有可能的 N×N 个点积,然后才将那些“未来”位置的分数屏蔽掉。
内存消耗也是 On2。 无论是否掩码,我们都需要存储这个 N×N 的注意力分数矩阵。
标准自注意力(Encoder): 第一个词元的 Query 会和所有 N 个 Key 计算点积。
掩码自注意力(Decoder): 第一个词元的 Query 只会和它自己计算点积。
无论哪种情况,自注意力的整体计算和内存复杂度都是 On2,因为都需要处理一个 N×N 的注意力分数矩阵。
一些解决方案:
工程角度:
- 检索增强生成 (Retrieval-Augmented Generation, RAG)
- Agentic Frameworks (代理框架) / 多智能体协作
- 上下文窗口管理与压缩技术
- 外部记忆模块
大模型算法角度(优化自注意力机制,以降低其On2的计算和内存复杂度,从而允许模型处理更长的序列):
- 稀疏注意力 (Sparse Attention)
等等
Tips:
大模型本身只能接收一次输入,然后输出,大模型本身是无记忆无状态的。(当然也有算法优化点)
大模型产品的“记忆”和“多轮对话能力”,很多是产品层面的巧妙设计和工程实现。
references:
Attention is all you need
十分钟理解Transformer
2.3 Post-Training
pandas
作用: pandas是一个强大的数据分析和处理库。它提供了DataFrame(数据框)这种灵活的数据结构,让您可以轻松地加载、清洗、转换和分析表格数据。
在LLM微调中的角色: 在微调LLM之前,您通常需要准备大量的文本数据。pandas可以帮助您处理这些数据,例如从CSV或数据库中读取数据、筛选相关信息、处理缺失值、对文本进行初步的清洗和组织,以便后续输入到LLM模型中。
scikit-learn
作用: scikit-learn是一个广泛使用的机器学习库,提供了各种分类、回归、聚类、降维等算法,以及模型选择和预处理工具。
在LLM微调中的角色: 尽管LLM属于深度学习领域,但scikit-learn在数据预处理阶段仍有其用武之地,例如特征工程、数据标准化或分割训练集/验证集/测试集。有时,您也可能用它来构建一些基线模型,与您微调后的LLM进行性能对比。
NumPy
作用: NumPy是Python中进行科学计算的基础库,提供了高性能的多维数组(ndarray)对象,以及处理这些数组的各种数学函数。
在LLM微调中的角色: 它是许多其他科学计算和深度学习库(如TensorFlow、PyTorch)的底层依赖。LLM内部处理的文本数据,无论是词嵌入还是模型的中间激活值,最终都会被表示为NumPy数组进行高效的数值运算。
Keras
作用: Keras是一个高级神经网络API,旨在实现快速实验。它以用户友好、模块化和可扩展性为设计目标,让构建、训练和评估深度学习模型变得非常简单。尤其是进入 TensorFlow 2.0 时代后,Keras 和 TensorFlow 的关系变得更加紧密
在LLM微调中的角色: Keras通常作为TensorFlow的顶层接口。它简化了构建复杂神经网络(包括LLM的结构)的过程,您可以利用Keras的API来定义模型的层、编译模型、进行训练和评估,从而更专注于模型的设计而非底层实现细节。
Transformers
作用: 这是由Hugging Face开发的一个非常重要的库,专门用于处理Transformer模型。它提供了大量预训练的Transformer模型(如BERT、GPT、T5等)及其对应的分词器(Tokenizer)。
在LLM微调中的角色: 对于LLM微调课程来说,Transformers库是核心中的核心:
加载各种预训练的LLM模型。
使用对应的分词器将文本数据转换为模型可以理解的数字格式。
在特定任务上对这些预训练模型进行微调(Fine-tuning)。
进行模型的评估和推理。
TensorFlow
作用: TensorFlow是由Google开发的一个开源机器学习平台。它提供了一个全面的生态系统,包括各种工具、库和社区资源,用于构建和部署机器学习应用。
在LLM微调中的角色: 它是Keras的底层计算引擎,为复杂的深度学习计算提供强大的支持。负责处理模型的张量运算、梯度计算和硬件加速(如GPU)。
PyTorch
由 Facebook (现在是 Meta) 的 AI 研究团队开发并开源的机器学习库。它主要用于构建和训练神经网络,尤其在深度学习研究和应用中非常流行。
Hugging Face
Transformers库:这是 Hugging Face 著名的 Python 库,提供了预训练模型(包括各种 LLM,如 BERT, GPT, T5, Llama, Mistral 等)及其对应的分词器 (Tokenizer)。它支持 PyTorch、TensorFlow 和 JAX 等主流深度学习框架。
Hugging Face Hub (模型、数据集和空间):这是一个平台。
Models (模型): 开发者和研究人员可以在这里上传、分享和下载各种预训练模型。
Datasets (数据集): 提供了大量用于训练和评估模型的数据集。
Spaces (空间): 允许用户托管交互式的机器学习演示应用(Web App),方便展示模型效果。
其他库:例如 Accelerate 用于分布式训练,Diffusers 用于扩散模型,PEFT 用于参数高效微调等。
Ollama
本地 LLM 运行: 它的主要目标是让用户能够在自己的笔记本电脑、台式机等消费级硬件上轻松运行各种开源 LLM。
模型库: Ollama 提供了模型列表(如 Llama等),这些模型通常经过优化和量化(例如 4-bit 量化),以便在本地资源有限的环境中高效运行。
简单命令行界面: 通过简单的命令(如 ollama run llama2),用户就可以下载模型并在本地启动一个 LLM 服务。
API 接口: Ollama 会在本地启动一个服务,并提供一个 RESTful API,方便开发者通过代码与本地运行的 LLM 进行交互。
(训练、微调、创建或分享 LLM,Hugging Face。本地运行和使用现有的开源 LLM,Ollama)
2.3.1 Fine-tuning
Core is DATA
SFT, Supervised Fine-Tuning
The Data you need in Fine-tuning:
Pairs: {input, target output}
(Tips: For reasoning: target output = “think” + answer)
数据形式: 通常是 (输入, 期望输出) 的配对。
分训练集、验证集、测试集(跟传统那一套很像)
输入-输出对案例1:
Input(输入):这部分代表了在微调过程中输入给模型的数据。
- 它是一个对话序列,用 <user> 和 <assistant> 标签标记。这通常被称为“轮次”或“对话历史”。
- 模型被展示了一系列问题和答案:
Input
用户: "What's the capital of France?"(法国的首都是什么?)
助手: "Paris"(巴黎)
用户: "What about Spain?"(那西班牙呢?)
助手: "Madrid"(马德里)
用户: "Germany?"(德国呢?)
Target Output(目标输出):这是我们希望模型在给定前面上下文的情况下,为最后一个用户查询生成的正确或期望的答案。
Target Output
Berlin
上下文理解能力:通过提供完整的对话历史作为 Input,模型学会理解对话的流程和上下文。它会明白 "Germany?" 是一个后续问题,询问的是首都,就像之前关于法国和西班牙的问题一样。
期望的行为:Target Output 明确地告诉模型,对于这个特定的输入,正确的响应应该是什么。在微调过程中,模型会调整其内部参数,以便当它看到像图中所示的输入时,更有可能生成 "Berlin" 作为答案。
案例2:
Input:
Alice has 3 apples and buys 2 more. How many now?
Target Output:
<think>
Start with 3.
Buys 2 => 3+2=5.
</think>
<answer>
"5"
</answer>
SFT的核心流程
- 数据准备与清洗
对于大多数任务,几千到几万条高质量数据通常是一个好的起点,其效果往往优于大量低质量数据。划分为训练集、验证集(用于监控训练和调参)和测试集(用于最终评估)。和深度学习那一套类似。
去重、过滤、格式化与规范化、错误修正与人工审核、偏见与安全性缓解
去重:数据泄漏(Data Leakage)
相似分布的数据在不同数据集中泄漏,也会毁掉你的数据划分。这意味着,如果你的训练集和测试集中包含了相同或非常相似的数据,那么你的模型在测试集上的表现就会被“虚高”,因为它实际上已经“见过”这些数据了。这会让你对模型的真实泛化能力产生错误的判断。看似无关但实际上语义相似的数据点,不仅要在训练集内部、测试集内部进行去重,更重要的是,要在训练集、验证集和测试集之间进行全面的去重。
在 “Train” (训练集) 中,有一个指令是 “How do I change my password on this platform?”(我如何在这个平台上更改密码?)“MinHash ≈” 在 “Test” (测试集) 中,有一个指令是 “What’s the process for updating my password here?”(在这里更新密码的流程是什么?)
表明它们是语义上非常相似的指令,尽管措辞不同。
同样,训练集中的 “Thank you for contacting us about” 和测试集中的 “Thanks so much for reaching out regarding” 也是近似重复的。
将这些相似的例子放在测试集中是一个错误,因为它会导致数据泄漏。
解决方案: Control (near) duplicates: de-dupe within and across splits" (控制(近似)重复数据:在数据划分内部和跨数据划分进行去重)
这意味着,不仅要在训练集内部去重,更重要的是,要确保训练集、验证集和测试集之间没有重复或高度相似的数据。
具体的去重技术:
MinHash/LSH (Locality Sensitive Hashing): 这些是高效的算法,用于发现大规模数据集中的近似重复项。
template- and paraphrase-aware: 这表示去重方法应该足够智能,能够识别那些使用不同模板或不同措辞表达相同意思的句子。
其他还有:
避免Randomly split, “same prompt, different target” across sets: 这指的是要特别避免在训练集中有一个指令 A 对应响应 X,而在测试集中有指令 A(或其语义相似变体)却对应响应 Y 的情况。这不仅是数据泄漏,还可能引入标签不一致的问题,混淆模型。
解决方案:许多实际应用中(特别是涉及时间序列或不断演进的数据)更稳健、更真实的划分方法。时间划分(Time Splitting):
将较早的数据用于训练。将较晚的数据用于验证和测试。
好处: 这种方法能够帮助模型更好地“generalizing into the future”(泛化到未来)。因为在真实世界中,模型总是需要处理它在训练之后才出现的新数据。如果你的模型在训练数据和测试数据之间存在时间上的重叠或倒置,那么它在测试集上的表现可能无法真实反映其未来性能。
-
模型选择与加载:选择合适的基座模型
-
训练配置:设置合适的训练参数,引导模型高效学习
最佳实践:
损失函数: 通常使用交叉熵损失(Cross-Entropy Loss),它衡量模型预测的下一个词元概率分布与实际目标词元之间的差异。
参数高效微调(PEFT): 对于超大型模型,强烈推荐使用 LoRA (Low-Rank Adaptation)、QLoRA、Prefix-Tuning 等 PEFT 方法。它们能显著减少训练成本、降低过拟合风险,同时保持甚至超越全参数微调的效果。这通常意味着不需要更新所有参数。
学习率调度(Learning Rate Scheduling): 使用学习率衰减策略(如 Cosine Annealing),在训练初期使用较高学习率快速探索,后期逐渐降低以稳定收敛。
优化器: AdamW 是目前 LLM 微调中最常用的优化器。
批量大小(Batch Size)与梯度累积: 根据 GPU 内存选择合适的批量大小。如果批量大小受限,可以使用梯度累积来模拟更大的批量,以获得更稳定的梯度估计。
早停(Early Stopping): 监控验证集上的性能,当模型性能不再提升时及时停止训练,避免过拟合。
- 训练执行
流程:
前向传播: 将数据输入模型,生成输出。
计算损失: 损失函数比较模型输出与预期响应。
反向传播: 根据损失计算模型参数的梯度。
参数更新: 优化器根据梯度调整模型参数,以最小化损失。
迭代: 重复上述步骤,遍历训练集多次(epoch)。
- 评估与部署
底层机制和基本原理确实和几年前训练 CNN、RNN 等深度学习模型时是一脉相承的, 深度学习的损失函数和反向传播——确实没有改变,但在实践层面和最佳实践上发生了巨大的变化
现在最流行的大模型领域,SFT 依然是核心步骤,但它被赋予了新的内涵和挑战,尤其体现在:
PEFT 的广泛应用,使得微调成本可控。
高质量指令数据的极端重要性,决定了模型能否真正“听懂人话”。
对齐人类意图的训练目标,不仅仅是预测正确。
2.3.2 Reinforcement Learning
Core is Grader
The Data you need in RL data:
List: {inputs} -> Tuples: {input, output, reward} from input + graders + environment
数据形式: 通常是 (输入, 模型输出, 奖励) 的轨迹(trajectory)。
[Optional] Tuples: {input, output A, output B, preference=which is better} for reward model
"rollout"是指一个完整的输入(Input)和模型输出(Model Output)的组合,即一个 {input, output} 元组。当一个强化学习代理(Agent,在这里就是你的语言模型)接收到一个输入(比如一个问题),然后它根据自己的策略生成一系列的思考过程和最终答案,这个从接收输入到生成完整输出的整个过程,就叫做一次“rollout”。它记录了模型在面对特定输入时,是如何“展开”其内部思考并给出结果的。
"trajectory(轨迹)"一个轨迹是比“rollout”更完整的概念,它不仅包含了输入和输出,还包含了模型因其输出而获得的奖励。{input, output, reward} 这样的元组构成了强化学习的核心数据单元。
- 拆解:
大模型(Agent)生成输出(Action):
你给大模型一个输入(Prompt),这在RL中相当于一个状态(State)。
大模型(现在是RL中的Agent,即“执行者”)根据其当前的策略(Policy),逐个生成词语(Token),形成一段完整的文本输出。生成每个词语都是一个动作(Action)。整个输出序列就是一系列动作的组合。
- 另一个模型(Reward Model)评分(Reward):
大模型生成完整的输出后,这个输出会被送给另一个独立的模型,我们称之为奖励模型(Reward Model, RM)。
奖励模型会评估大模型输出的质量,并给出一个标量分数,这就是奖励(Reward)。这个分数代表了输出的好坏程度。
例如,如果输出是高质量、有帮助、无害的,奖励模型可能给出高分(正奖励)。
如果输出是低质量、不相关、有害的,奖励模型可能给出低分(负奖励)。
奖励模型本身通常也是一个经过监督学习训练的模型,它学习了如何根据人类的偏好或预设的规则来评价文本。
奖励模型接收的是一个完整的 (输入Prompt, 大模型生成的完整Output) 对,然后给出一个单一的、标量化的分数,来评估这个完整输出的质量、相关性、安全性等。
既然奖励模型只给出了一个最终的、整体的奖励,那么强化学习算法(比如PPO)是如何利用这个单一的奖励来调整大模型在生成过程中每一步的参数呢?这正是信用分配问题的核心所在。
RL算法并不会对每个 action 或 state 进行单独评分,而是通过巧妙的机制,将最终的整体奖励“回溯”到导致这个奖励的每一步决策上。
流程:
1.收集轨迹(Trajectory): 大模型(Agent)接收一个 Prompt (初始 state),然后一步一步地生成 action (词元),直到生成完整的 Output。这个 (state_0, action_0, state_1, action_1, …, state_T, action_T) 的序列就是一条轨迹(Trajectory)。
2.获得整体奖励: 这条完整的轨迹(或者说最终的 Output)被送给奖励模型,奖励模型给出一个单一的标量奖励 R。
3.信用分配与策略更新:
RL算法(例如PPO)会分析这条轨迹中每一个 action (生成词元) 的概率。
如果最终的奖励 R 是高的(正奖励),算法会认为这条轨迹中的所有 action 都是“好”的,并会尝试增加这些 action 在相应 state 下被选中的概率。
如果最终的奖励 R 是低的(负奖励),算法会认为这条轨迹中的所有 action 都是“坏”的,并会尝试降低这些 action 在相应 state 下被选中的概率。
这个过程是通过计算策略梯度(Policy Gradient)来实现的。策略梯度会结合奖励 R 和每个 action 的对数概率(log-probability),来计算出如何调整大模型参数的方向和幅度。
更高级的算法(如PPO)还会使用优势函数(Advantage Function),它能更精确地衡量某个特定动作在某个特定状态下的“好坏程度”,从而更有效地分配信用。优势函数会考虑当前动作与平均动作相比,能带来多少额外的奖励。
- 强化学习算法调整大模型参数:
奖励模型给出的这个标量奖励,不像监督学习中的损失函数那样,可以直接提供一个明确的梯度方向。不能直接把 +2 或 -1 这样的分数反向传播到大模型的参数上。
强化学习算法的作用就是将这个标量奖励信号,转化为可以用于更新大模型参数的“有效梯度”。
这里常用的算法是**策略梯度(Policy Gradient)**方法,其中最流行且有效的是**近端策略优化(Proximal Policy Optimization, PPO)**。
* **策略梯度算法的核心思想:**
* **目标:** 提高那些导致高奖励的动作(即生成高奖励文本的词语序列)的概率,降低那些导致低奖励的动作的概率。
* **如何实现?** 算法会查看大模型在生成输出序列时,每个词语被选中的**概率**。
* 当获得一个**正奖励**时,算法会“鼓励”大模型,让它在未来遇到类似情况时,更有可能生成刚才那些导致高奖励的词语序列。
* 当获得一个**负奖励**时,算法会“惩罚”大模型,让它在未来遇到类似情况时,更不可能生成刚才那些导致低奖励的词语序列。
* 具体来说,它会计算一个梯度,这个梯度是基于奖励信号和生成序列中每个词语的对数概率(log-probability)来加权的。这个梯度会告诉大模型如何调整其参数,以便在未来生成更高奖励的输出。
* **PPO 算法的优势(以 PPO 为例):**
* PPO 是一种**Actor-Critic**算法。大模型就是“Actor”(执行者),它负责生成文本。通常还会有一个“Critic”网络(价值函数),它学习预测在给定状态下能获得的预期奖励。
* PPO 引入了一个**裁剪(Clipping)**机制,确保在更新策略时,新的策略不会与旧策略偏离太远。这有助于训练的稳定性和效率。
* PPO 会计算一个**优势函数(Advantage Function)**,它衡量某个动作比平均动作好多少。这个优势函数结合奖励和价值函数来指导策略更新。
* **总结参数调整:**
1. 大模型生成一个输出序列。
2. 奖励模型给出这个序列的奖励 `R`。
3. RL算法(如PPO)利用这个 `R` 和大模型生成每个词语时的**概率**,计算出一个“伪梯度”或“有效梯度”。
4. 这个有效梯度被用来通过反向传播**更新大模型的参数**,使得大模型在未来更有可能生成高奖励的输出。
-
Q-table: 主要适用于离散且状态空间和动作空间都相对较小的问题. 处理的是连续或高维的状态空间和动作空间,大模型这种,就用不了.
-
对于LLM的RLHF(Reinforcement Learning from Human Feedback)过程,预训练的LLM本身就成为了强化学习中的策略网络 (Policy Network),或者说,它就是那个“智能体”的核心.
-
名词解释
智能体 (Agent): 预训练并经过SFT(监督式微调)的语言模型。
环境 (Environment): 这里的“环境”不是一个物理世界,而是一个抽象的概念。它接收智能体(LLM)生成的文本,并根据预先训练好的奖励模型 (Reward Model, RM) 给出奖励。
状态 (State): 通常是给LLM的输入提示 (Prompt) 或当前的对话历史。
动作 (Action): LLM在给定状态下生成的一个词元 (token)。由于LLM是自回归的,它会连续生成一系列词元,直到生成完整的响应。
策略 (Policy): LLM的参数(权重和偏置)定义了它的策略。给定一个输入状态(Prompt),策略(LLM)会输出生成下一个词元的概率分布。我们希望通过RL来调整这些参数,使得LLM生成的整个响应序列能够获得更高的奖励。
奖励 (Reward): 由奖励模型 (Reward Model, RM) 提供。RM是一个独立的神经网络,它接收LLM生成的完整响应和原始Prompt,然后输出一个标量值,表示这个响应有多“好”(例如,多有用、多安全、多符合人类偏好)。
- 在RLHF中,最常用的算法之一是近端策略优化 (Proximal Policy Optimization, PPO)。PPO是一种策略梯度 (Policy Gradient) 算法,它直接学习一个策略,而不是像Q-learning那样学习Q值。
PPO算法的核心组件:
-
策略网络 (Policy Network / Actor):
作用: 预训练并经过SFT的LLM。它接收当前状态(Prompt),然后生成一个动作(下一个词元)。它输出的是每个词元的概率分布。
更新方式: PPO会直接调整这个网络的参数,使其在未来生成更高奖励的动作序列。 -
价值网络 (Value Network / Critic):
作用: 这是一个独立的神经网络(通常与策略网络共享一部分底层参数,或完全独立)。它接收当前状态(Prompt和已生成的词元),并估计从这个状态开始,遵循当前策略能获得的预期累积奖励。
更新方式: 通过监督学习的方式进行训练,目标是最小化其预测值与实际累积奖励之间的误差。 -
奖励模型 (Reward Model, RM):
作用: 如前所述,它是一个预先训练好的监督学习模型,用于评估LLM生成响应的质量。它不参与RL训练过程中的参数更新,只提供奖励信号。
在RLHF过程中,通常涉及到以下三个核心模型:
-
基础大语言模型 (Base Large Language Model, LLM)
角色: 最初预训练并经过监督式微调(SFT)的那个模型。在强化学习阶段,它扮演着智能体 (Agent) 的角色,具体来说,它是策略网络 (Policy Network)。它的任务是根据给定的提示(Prompt)生成文本。
训练:
预训练: 在海量无标签数据上进行自监督学习,学习语言的通用知识。
监督式微调 (SFT): 在高质量的指令-响应对数据集上进行监督学习,使其学会遵循指令。
强化学习阶段: 在RLHF的最后一步,这个模型的参数会根据奖励模型的反馈进行进一步的调整和优化。 -
奖励模型 (Reward Model, RM)
角色: 这是一个独立的神经网络。它不直接生成文本,它的任务是接收一个提示(Prompt)和模型生成的一个响应,然后输出一个标量分数,表示这个响应有多“好”(例如,多有用、多安全、多符合人类偏好)。在强化学习的框架中,它充当了环境的奖励函数。
训练: 它需要专门的训练
数据来源: 收集大量由人类标注员对模型生成文本进行偏好排序的数据。例如,给LLM一个Prompt,让它生成3-4个不同的响应,然后让人类标注员对这些响应进行排序(A比B好,B比C好)。
训练方式: 这是一个监督学习任务。奖励模型被训练来预测人类的偏好。它的损失函数通常设计为,如果它对人类更喜欢的响应给出更高的分数,那么损失就小。通过这种方式,RM学会了模仿人类的偏好。 -
价值网络 (Value Network)
角色: 这也是一个独立的神经网络(尽管有时它可能与策略网络共享底层参数,或者只是策略网络的一个“头部”)。它的任务是接收当前的状态(例如,Prompt和LLM已经生成的部分文本),并预测从这个状态开始,策略网络(LLM)最终能获得的总累积奖励。它在强化学习算法(如PPO)中扮演评论家的角色,帮助策略网络更有效地学习。
训练: 它需要训练
数据来源: 在RLHF训练过程中,它会根据策略网络与环境(奖励模型)的交互所获得的实际奖励进行训练。
训练方式: 这是一个监督学习任务。价值网络被训练来预测实际的累积奖励。它的损失函数通常是均方误差,即最小化其预测值与实际累积奖励之间的差异。
现成性: 通常没有现成的。它是在RLHF训练过程中,与策略网络(LLM)一起训练的。
2.3.3 Fine-tuning V.S. RL
Fine-tuning: 准备 (输入, 期望输出) 数据对,通过损失函数直接计算预测与期望之间的差异,然后反向传播调整参数。这是一个直接的、有监督的参数调整过程。
RL: 准备 (输入, 大模型输出, 奖励) 轨迹。奖励模型给出标量评分。这个评分不是直接反向传播,而是通过强化学习算法(如PPO)间接地计算出如何调整大模型参数的梯度,以最大化未来的累积奖励。这是一个探索-奖励-学习的循环过程。
- Fine-tuning:深度学习的经典应用
Fine-tuning 是深度学习的经典训练模式。
损失函数: 在 Fine-tuning 中,我们会定义一个明确的损失函数(例如,对于文本生成任务,通常是交叉熵损失)。这个损失函数会衡量大模型预测的下一个词的概率分布与真实标签(期望输出)之间的差异。
反向传播: 损失函数计算出的损失值,会通过反向传播算法,沿着大模型的神经网络结构,高效地计算出每个参数对这个损失的贡献(即梯度)。
参数调整: 优化器(如Adam、SGD等)会根据这些梯度,直接调整大模型的参数,目标是使损失函数的值最小化,从而让模型的预测更接近期望输出。
所以,Fine-tuning 就是把一个预训练好的深度神经网络(大模型)拿过来,用新的数据集和监督学习的方法,继续进行深度学习训练。
- RL(例如 PPO)调整大模型:深度学习与强化学习的结合
强化学习,大模型的参数更新也依然依赖于深度学习的损失函数概念和反向传播。
奖励(Reward)与损失函数(Objective Function): 在RL中,奖励模型给出的奖励信号本身不是一个可以直接反向传播的损失函数。但是,强化学习算法(如PPO)会构建一个特殊的“策略损失函数”或“目标函数”。这个目标函数是精心设计的,它会结合奖励信号和模型在生成过程中采取动作的概率。
这个策略损失函数的目标是最大化预期累积奖励。
例如,PPO的目标函数会鼓励模型在未来更多地选择那些导致高奖励的动作,并减少选择导致低奖励的动作。
反向传播: 一旦这个“策略损失函数”或“目标函数”被构建出来,它的梯度就可以通过反向传播算法,沿着大模型的神经网络结构计算出来。
参数调整: 优化器会根据这些通过反向传播计算出的梯度,调整大模型的参数。这个调整的目的是使大模型在未来能够生成更高奖励的输出序列。
- 市场上最先进的大型语言模型,它们的训练和优化过程都离不开深度学习的核心概念:
深度神经网络: 它们本质上都是极其庞大和复杂的深度神经网络(主要是 Transformer 架构)。
损失函数: 在预训练和监督微调阶段,都会使用损失函数(如交叉熵损失)来衡量模型预测与真实数据之间的差异。
反向传播: 这是优化这些深度神经网络参数的基石。无论损失函数多么复杂,最终都要通过反向传播算法来计算梯度。
优化器: 梯度计算出来后,优化器(如 AdamW)会根据这些梯度来更新模型的数亿甚至数万亿个参数。
即使是基于强化学习的对齐(如 RLHF),也是在深度学习的框架内进行的:
RLHF 并不是抛弃了深度学习,而是将强化学习的原理应用于深度神经网络的训练。
奖励模型(Reward Model)本身就是一个深度神经网络,它通过监督学习(人类反馈数据)训练,并输出一个标量奖励。
用于生成文本的大模型(Policy Model)在 RLHF 阶段,其参数更新依然是通过策略梯度方法(如 PPO)来计算一个等效的“损失”或“目标函数”,然后通过反向传播来调整其参数。这个过程的最终目的,是让大模型生成更高奖励的文本。
所以,可以这样理解:深度学习提供了构建和训练这些庞大模型的基础架构和机制,而强化学习(特别是 RLHF)则提供了一种更高层次的“指导信号”和“优化目标”,来引导这些深度学习模型学习更符合人类偏好和价值观的行为。
- 微调是全参数再训练吗?
传统意义上的微调(Full Fine-tuning):
在深度学习的早期和中期,以及对于参数量不是特别巨大的模型,微调通常意味着更新模型的所有参数。你加载一个预训练模型,然后用你的特定任务数据和损失函数,让所有层的所有参数都参与梯度计算和更新。你的理解——“全参数再训练”——在传统意义上是完全正确的。
现代大模型中的微调:参数高效微调(Parameter-Efficient Fine-Tuning - PEFT):
随着大型语言模型(LLM)的参数量达到数百亿甚至万亿级别,更新所有参数变得极其昂贵(需要大量的计算资源和存储),并且在小数据集上容易导致过拟合。
因此,出现了一系列被称为**参数高效微调(PEFT)**的方法。这些方法的核心思想是:
只更新模型参数的一小部分。
或者在模型中引入少量新的、可训练的参数,而冻结大部分预训练参数。
常见的 PEFT 方法包括:
* **LoRA (Low-Rank Adaptation):** 在模型的关键层(如注意力层)中插入小的、可训练的低秩矩阵,只更新这些小矩阵的参数。
* **Prefix-Tuning / Prompt-Tuning:** 在输入序列前添加少量可训练的“虚拟”token,只优化这些虚拟 token 的嵌入向量,而不触及模型的主体参数。
* **Adapter-based methods:** 在模型的每一层之间插入小的适配器模块,只训练这些适配器模块的参数。
总结:
“微调”可以指全参数再训练,特别是在计算资源充足且数据集较大的情况下。
但对于当今最先进的超大型模型,为了提高效率、减少资源消耗并防止过拟合,**参数高效微调(PEFT)**方法变得越来越流行,并且通常被认为是“微调”的一种重要形式。在这种情况下,不是所有参数都被更新。
2.3.4 Encode text efficiently - Token (Tokenizer & Embedding)
-
people encode:
Words (dictionary)
Characters (alphabet) -
But Tokens encode more efficiently?
Algorithms like BPE compress training text into most efficient strings: “ing”
All tokens = model vocabulary词汇表(map, token - ID number)
GPT-3 has 50k BPE tokens
Tokenizer can do this -
Tokenizer
Tokenizer可以看作是一种传统的、基于统计和规则的机器学习模型。
Tokenizer的“训练”是基于统计学和算法规则的。它需要一个大型的文本语料库来分析语言的模式。目的是为了:1.将原始文本分解成一个个有意义的最小单元(Token)并映射为数字ID,构建一个高效的词汇表 (Vocabulary)。2.学习一套将文本切分成这些词汇表中单元的规则。
Tokenizer的主要任务是作为大型语言模型(LLM)的文本输入和输出的桥梁。它负责:1.编码 (Encoding):将人类可读的原始文本(字符串)转换成模型能够理解的数字序列(Token ID 序列)。2.解码 (Decoding):将模型输出的数字序列(Token ID 序列)转换回人类可读的文本。
“训练”过程最终会产出一套固定的规则和词汇表。当 Tokenizer 接收到新的文本时,它会严格按照这些规则来切分文本并映射到 ID。这保证了编码过程的确定性和一致性。
BPE (Byte Pair Encoding) 为例,它的“训练”,过程大致:
1.初始化: Tokenizer 首先将所有文本分解成单个字符,并将这些字符作为初始词汇表。
2.统计频率: 它会遍历语料库,统计所有相邻字符对(或子词对)的出现频率。
3.合并最频繁对: 找到语料库中最频繁出现的字符对(例如,如果 “e” 和 “s” 经常一起出现,它可能会将它们合并成 “es”)。
4.更新词汇表和规则: 将这个新的合并单元 “es” 添加到词汇表中,并记录下这个合并规则。
5.重复: 重复步骤 2-4,不断地合并最频繁出现的子词对,直到达到预设的词汇表大小上限,或者达到预设的合并次数。
案例
如果输入单词 "strawberry":
Tokenizer 会尝试匹配最长的已知 Token。
可能会发现 straw 在词汇表中 (ID 9)。
可能会发现 berry 在词汇表中 (ID 10)。
所以 "strawberry" 会被编码成 [9, 10]。
如果输入 "unbelievable":
可能会发现 un 在词汇表中。
可能会发现 believe 在词汇表中。
可能会发现 able 在词汇表中。
所以 "unbelievable" 可能会被编码成 [un_ID, believe_ID, able_ID]。
BPE等子词Tokenization算法的巨大优势。它能显著减少表示一段文本所需的Tokens数量,从而提高模型的训练和推理效率,减少计算资源消耗,并可能让模型处理更长的文本上下文。这些Tokens可能是一个完整的单词(比如"apple"),也可能是一个单词的一部分(比如"ing"、“sub”),甚至是单个字符。它们是模型在处理文本时使用的最小有意义的单位。
平衡OOV和序列长度:如果词汇表太小(比如只包含字符),那么序列会非常长,模型处理效率低,且单个字符语义信息少。如果词汇表太大(比如包含所有可能的单词),则会导致 OOV 问题严重,且 Embedding Matrix 过于庞大。Tokenizer 的“训练”就是为了找到一个最佳平衡点,使得词汇表既能覆盖大部分常见词,又能通过子词组合处理生僻词。
(OOV (Out-Of-Vocabulary) 问题:如果遇到训练集中从未出现过的新词,模型就无法处理,通常会用一个特殊的 < UNK > (Unknown) Token 来表示,这会丢失信息。)
- Embedding
Embedding层想象成一个巨大的查找表 (lookup table),更准确地说是一个矩阵 (matrix)。称之为 Embedding Matrix。
结构:
这个矩阵的行数等于模型的词汇表大小 (vocabulary size)。也就是说,词汇表中有多少个唯一的 Token ID,这个矩阵就有多少行。
这个矩阵的列数就是我们设定的嵌入维度 (embedding dimension)。这个维度通常是一个超参数,比如 768、1024、2048 等。这个数字代表了每个 Token 的语义信息被编码成了多少个浮点数。
对应关系:
每一行都对应着词汇表中的一个唯一的 Token ID。
该行中的所有浮点数就构成了这个 Token ID 对应的嵌入向量。
例
假设词汇表有50,000个Token ID并且你设置的嵌入维度是768。那么Embedding Matrix就会是一个50,000 x 768 的矩阵
当模型接收到Token ID 2640时,它会去Embedding Matrix 的第 2640 行查找。
这一整行 [0.038, 0.002, 0.141, ..., -0.042] (共 768 个浮点数) 就是 Token ID 2640 的嵌入向量。
浮点数向量
在模型训练开始时,Embedding Matrix 里的所有浮点数通常是随机初始化的。
一开始,这些向量没有任何意义。模型会读取大量的文本数据,经过数百万甚至数十亿次的学习和调整,模型也会不断地调整 Embedding Matrix 中的浮点数,Embedding Matrix 中的每个向量就逐渐“学会”了它所代表的 Token 的语义信息。
通过这种迭代的训练,Embedding Matrix 中的浮点数会逐渐收敛。语义相似的 Token(比如“国王”和“女王”)它们的嵌入向量在向量空间中会彼此靠近。
具有特定关系的 Token(比如“国王” - “男人” + “女人” = “女王”)它们的向量之间会呈现出可捕捉的数学关系。
上下文信息被编码到向量中,使得向量能够代表 Token 在不同语境下的含义。
Tokenizer 和 Embeddings 协同
Tokenizer 和 Embeddings 协同
Tokenizer (分词器) 负责“数字化”文本: 它接收原始文本,将其切分成 Tokens,然后将每个 Token 转换成一个唯一的整数 ID。
"What words are indivisible?" (文本)
-> ["What", "words", "are", "indivis", "ible", "?"] (Tokens)
-> [2640, 3073, 418, 3221, 21142, 30] (Token IDs)
Embeddings (嵌入层) 负责“语义化”数字: 它接收这些 Token ID,并为每个 ID 查找或生成一个具有丰富语义信息的浮点数向量。
[2640, 3073, 418, 3221, 21142, 30] (Token IDs)
-> [[0.038, 0.002, ...], [0.440, 0.443, ...], ...] (嵌入向量序列)
整体:
Embedding Matrix 中的每一个浮点数,都和神经网络中的其他权重(weights)一样,是可训练的参数。
当模型开始训练时,它会接收大量的文本数据。这些文本首先通过 Tokenizer 转换为 Token ID 序列。
查找 (Lookup): 对于输入的每个 Token ID,Embedding 层会去 Embedding Matrix 中查找对应的行(即该 Token ID 的嵌入向量)。
前向传播 (Forward Pass): 这些嵌入向量会作为输入,流经神经网络的后续层(例如 Transformer 编码器/解码器)。模型会尝试完成某个任务(比如预测下一个词、翻译、回答问题等)。
计算损失 (Loss Calculation): 模型会比较它的预测结果与真实结果之间的差异,计算出一个“损失值”,这代表了模型表现的“好坏”。
反向传播 (Backpropagation) 与优化: 如果模型预测错误,它会使用反向传播算法,根据损失值来计算每个参数(包括 Embedding Matrix 中的浮点数)应该如何调整,才能让损失值降低。
迭代更新: 这个过程会重复数百万、数十亿次,模型会不断地调整 Embedding Matrix 中的浮点数。
- LLM Embedding层和RAG中Embedding model的语义
RAG 中的 Embedding 和 LLM 内部的 Embedding Matrix 所代表的“嵌入”概念在语义上是高度一致的,它们都旨在将文本(或其组成部分)转换为密集的数值向量,以捕捉其语义信息。在粒度、生成方式和具体用途上有所不同。
在自然语言处理(NLP)领域,“嵌入”的核心思想是:将离散的、符号化的信息(如单词、句子、文档)映射到一个连续的、低维的向量空间中。在这个向量空间里:
语义相似的项,它们的向量在空间中距离会更近。
语义不相似的项,它们的向量距离会更远。
这种数值表示方式使得计算机能够进行数学运算来理解和比较文本的含义。
- LLM 内部的 Embedding Matrix
LLM 内部的 Embedding Matrix,是 LLM 神经网络的第一层。
粒度: 它主要处理Token 级别的嵌入。每一个 Token ID(无论是单词、子词还是标点符号)都对应 Embedding Matrix 中的一行,即一个固定的嵌入向量。
生成方式: 这些嵌入向量是 LLM 在其预训练阶段通过反向传播和梯度下降学习出来的。它们是模型参数的一部分,与神经网络中的其他权重一样,会根据训练任务(如预测下一个词)进行调整和优化。
用途: 将离散的 Token ID 转换为连续的、稠密的数值表示,作为后续 Transformer 层(如注意力机制)的输入。这些 Token 嵌入是构建更高级语义表示的基础。
- RAG 中的 Embedding Model
在检索增强生成(RAG)系统中,“Embedding Model”通常是用来生成更大粒度文本块的嵌入。
粒度: 它通常用于生成句子、段落或整个文档级别的嵌入。LLM 内部的 Embedding Matrix 负责 Token 级别的嵌入。RAG 中的 Embedding Model 负责 Chunk 级别的嵌入。
生成方式: RAG 中的 Embedding Model 是一个独立的模型(或是一个专门针对嵌入任务微调过的语言模型)。它接收一个完整的句子或文档作为输入,然后输出一个单一的、固定长度的向量,代表整个输入文本的语义。这些模型通常基于 Transformer 架构,例如 Sentence-BERT、OpenAI 的 text-embedding-ada-002 或 Google 的 Universal Sentence Encoder。它们内部可能也包含一个 Token 级别的 Embedding Layer,但它们的功能是进一步处理这些 Token 嵌入,聚合成一个代表整个文本的向量。非常流行的 Sentence-BERT (SBERT) 系列模型,就是基于 BERT(一种 Transformer 编码器)进行微调,使其能够生成语义上有意义的句子嵌入。用于生成嵌入的模型通常比大型生成式 LLM 要小得多。
用途:
检索: 将知识库中的大量文档转换为嵌入向量,并存储在一个向量数据库中。
查询: 当用户提出查询时,将查询本身也转换为一个嵌入向量。
匹配: 通过计算查询嵌入与文档嵌入之间的相似度(如余弦相似度),来快速有效地找到与查询语义最相关的文档。这些检索到的文档随后会被作为上下文提供给 LLM 进行生成。
- RAG中Embedding的粒度 - Chunk:
RAG系统中,希望检索到的信息是足够具体以回答用户问题,但又不过于庞大以至于超出语言模型的上下文窗口。这就是粒度选择和 Chunking 变得至关重要的原因。
为了解决上述问题,引入了 Chunking 的概念。
Chunking 是指将原始文档(通常是较长的文档)分割成更小、更易于管理、且语义相对完整的“块”或“片段”。Chunck有很多策略。
一个 Chunk,无论它包含一个句子、一个段落还是多个段落,最终都会被 RAG 的 Embedding Model 转换成一个单一的、固定长度的浮点数向量。
RAG 中的 Chunk Embedding
在 RAG 系统中,首先将原始文档分割成一个个的 Chunk。一个 Chunk 可以是一个句子、一个段落,或者根据特定策略(如固定字符数、语义边界)划分出来的文本片段。
RAG 的 Embedding Model 是将这整个 Chunk 的文本内容,压缩成一个单一的、代表其整体语义的浮点数向量。一个 Chunk,无论其内部文本多长,经过 RAG Embedding Model 处理后,都只会输出一个固定长度的浮点数向量。
用户查询的 Embedding 向量(通过相同的 RAG Embedding Model 生成)。
知识库中每个 Chunk 的 Embedding 向量。
计算距离
向量数据库: 知识库中的所有 Chunk 向量都被存储在一个向量数据库中。
相似度计算: 当用户查询到来时,其 Embedding 向量会被用来与向量数据库中的所有 Chunk 向量进行相似度计算。最常见的方法是余弦相似度 (Cosine Similarity),它衡量了两个向量方向的接近程度。
检索: 向量数据库会返回与查询向量最相似(即余弦相似度最高)的 Top-K 个 Chunk。 检索时,比较的是查询的单一向量与每个 Chunk 的单一向量之间的相似度。
2.3.5 模型预测下个Token (From probabilities to next token id)
LLM 预测下一个词时,它不会直接给出一个词,而是会给出一个所有可能词的概率分布。然后,需要从这个分布中“选择”一个词作为下一个输出。两种主要的 Token 选择策略
“Greedy: highest probability”(贪婪策略:选择最高概率的)
这是最直接的方法。模型会直接选择在当前步中概率最高的那个 Token 作为下一个输出。
例如,在 0.5 0.2 0.3 ... 0.01 这个概率分布中,如果采用贪婪策略,模型会选择概率为 0.5 的那个 Token。
优点: 简单、确定性强。
缺点: 容易陷入局部最优解,生成的文本可能缺乏多样性和创造性,有时会显得重复或不自然。
“Sample: sample from distribution”(采样策略:从分布中采样)
这种策略会根据每个 Token 的概率,从整个概率分布中随机抽取一个 Token。概率越高的 Token,被抽到的机会就越大,但概率较低的 Token 也有机会被选中。
例如,在 0.5 0.2 0.3 ... 0.01 中,概率为 0.5 的 Token 有 50% 的机会被选中,概率为 0.3 的 Token 有 30% 的机会被选中,等等。
优点: 引入了随机性,生成的文本更具多样性、创造性和自然度,更符合人类语言的特点。
缺点: 每次生成的结果可能不同,有时可能会生成不那么“合理”的词(如果采样到了概率很低的词)。
为了更好地控制随机性,通常会结合 Temperature 和 Top-k / Top-p (nucleus) sampling 等技术
Beam Search束搜索
2.3.6 Epoch / Iterations / Batch / Padding / tensor / LLMs GPU并行计算
-
Epoch:一个 Epoch 就是指模型将整个训练数据集完整地遍历一遍。
-
Iterations (Epoch、Batch Size 和 Iteration)
训练数据集大小 (N): 你的总样本数量。
批次大小 (Batch Size / B): 每个批次包含的样本数量。
迭代次数 (Iterations / I) 或步数 (Steps): 在一个 Epoch 中,需要处理多少个批次才能遍历完整个数据集。
训练数据集大小 (N) = 迭代次数 (I) * 批次大小 (B)
例:
假设有一个包含 1000 个样本的训练数据集 (N = 1000)。
设置的批次大小是 100 (B = 100)。
那么:在一个 Epoch 中,模型需要进行 10 次迭代 (I = 1000 / 100 = 10)。
即模型会处理 10 个批次,每个批次包含 100 个样本,直到所有 1000 个样本都被处理过一次,一个 Epoch 就结束了。
如果设置了 epochs = 5,那么模型会重复这个过程 5 次,总共会进行 50 次迭代
- Batch
是为了高效利用 GPU 并行计算而将多个样本打包在一起的单位。
一个批次就是一组(通常是几十到几百个)独立的输入样本,它们被收集在一起,然后作为一个整体输入到模型中进行处理。例如,如果有 1000 句话要处理,可以把它们分成 10 个批次,每个批次包含 100 句话。
计算效率。这是最主要的原因。现代计算机硬件(尤其是 GPU)被设计用来进行大规模并行计算。一次处理一个样本,GPU 的大部分计算单元都会闲置。通过批处理,GPU 可以同时对批次中的所有样本执行相同的操作,从而大大提高计算吞吐量。
- Padding
模型需要固定形状的输入,而批次又需要将多个输入打包在一起。如果批次中的文本序列长度不一,我们如何将它们打包成一个固定形状的张量呢?Padding
Padding确保了批次中的所有序列长度一致,为 GPU 的高效并行计算提供了统一的数据结构。
代码案例
from transformers import AutoTokenizer
import torch
# 1. 加载预训练的 Tokenizer
tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")
# 2. 准备一个包含不同长度句子的批次
sentences = [
"Hello world.",
"This is a much longer sentence that needs to be processed.",
"A short one."
]
# 3. 使用 Tokenizer 对批次进行编码,并启用填充 (padding=True)
# 同时,为了配合模型,我们通常也需要返回 PyTorch 张量 (return_tensors="pt")
# 并可能需要截断过长的序列 (truncation=True)
encoded_inputs = tokenizer(
sentences,
padding=True, # 启用填充,使所有序列长度一致
truncation=True, # 如果序列超过模型最大长度,则进行截断
return_tensors="pt" # 返回 PyTorch 张量
)
# 4. 查看结果
print("Input IDs (padded):")
print(encoded_inputs['input_ids'])
# 示例输出可能这样:
# tensor([[ 101, 7592, 2088, 1012, 102, 0, 0, 0, 0, 0, 0, 0, 0, 0],
# [ 101, 2023, 2003, 1037, 2187, 6328, 2008, 1012, 102, 0, 0, 0, 0, 0],
# [ 101, 1037, 2715, 2028, 1012, 102, 0, 0, 0, 0, 0, 0, 0, 0]])
print("\nAttention Mask:")
print(encoded_inputs['attention_mask'])
# 示例输出可能这样:
# tensor([[1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
# [1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
# [1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]])
# 5. 解释输出
# - input_ids: 这就是经过填充的 Token ID 矩阵。可以看到短的句子后面补了0。
# 这里的0就是填充Token的ID。
# - attention_mask: 这是一个与 input_ids 形状相同的二进制矩阵。
# - 1 表示对应的 Token 是原始文本的一部分(需要模型关注)。
# - 0 表示对应的 Token 是填充 Token(模型在计算注意力时应该忽略它)。
# 这个 mask 确保了填充 Token 不会影响模型的实际语义理解。
# 6. 将这些输入张量传递给模型
# model = AutoModelForSequenceClassification.from_pretrained("bert-base-uncased")
# outputs = model(**encoded_inputs)
- tensor张量
在深度学习和物理学中,张量是一个非常通用的概念,可以看作是向量和矩阵的推广。
0维张量 (Scalar/标量): 一个单独的数字,没有方向。例如:5,3.14。
1维张量 (Vector/向量): 一串数字,有方向。例如:[1, 2, 3],[0.1, 0.5, 0.2]。
2维张量 (Matrix/矩阵): 数字的矩形排列,有行和列。
3维张量: 矩阵的堆叠。例如:彩色图像 (高 x 宽 x 颜色通道)。
更高维张量: 依此类推。
张量的“维度”或“秩 (rank)”指的是它有多少个索引来定位其中的一个元素。
例:
输入 ID 经过填充后,是二维张量: (batch_size, max_sequence_length)
经过 Embedding Lookup 后,输出的嵌入向量是三维张量: (batch_size, max_sequence_length, embedding_dimension)
- 训练过程
像大型语言模型 (LLM),以及其他基于 Transformer 架构的模型,在训练时也都是通过设置 Epoch、Batch 和 Padding 来进行的,其基本原理与传统 CNN 深度学习训练是高度一致的。
相同点:
都使用 Epoch、Batch 来组织训练过程,以提高计算效率和模型学习能力。
都严重依赖 GPU 的并行计算能力。
都使用优化器(如 Adam)通过反向传播和梯度下降来更新模型参数。
不同点(或侧重点不同):
Padding 的目的:
LLM (Transformer): 主要用于使批次中的变长序列统一长度,以便进行高效的批处理。
CNN: 在图像处理中,Padding 通常用于在卷积操作中保持特征图的空间尺寸(same padding),或确保卷积核能覆盖图像边缘信息。图像输入本身通常会被裁剪或缩放到固定大小,因此“批次内不同样本长度不一”的问题在图像输入层不那么常见。
输入数据结构:
LLM: 原始输入是变长文本序列,需要 Tokenization 和 Padding 转换为 (batch_size, max_sequence_length) 的二维张量(或更高维,如果考虑 embedding 维度)。
CNN: 原始输入通常是固定大小的图像,直接组织成 (batch_size, channels, height, width) 的四维张量。
2.3.7 math: Loss, gradients, weight updates & Train
Same as training NN
1. Token 到 Embedding 的转换
Tokenization (分词):首先,输入的文本(比如“What's the capital of California?”)会被分解成一个个的 token(例如:What, 's, the, capital, of, California, ?)。
Embedding (嵌入):每个 token 都会被映射到一个高维的向量空间中,这就是它的 embedding。初始时,这些 embedding 向量的数值确实是随机初始化的。在训练过程中,这些 embedding 会随着模型的学习而不断调整和优化,使得语义相似的 token 在向量空间中距离更近。
2. 模型内部处理 (LLM 的核心)
这些 embedding 向量会作为输入,通过 LLM 的多层结构(例如,Transformer 模型的多个编码器/解码器层)。
在这些层中,模型会进行复杂的计算,捕捉 token 之间的关系、上下文信息、语法结构和语义含义。每一层都会对 embedding 进行转换和精炼,生成更高级别的、包含更多上下文信息的隐藏状态 (hidden states)。
3. 从隐藏状态到 Logits (线性层)
当输入序列经过 LLM 的所有层处理后,模型会为每个位置(或要预测的下一个 token)生成一个最终的隐藏状态向量。这个向量包含了模型对当前上下文的理解。
这个隐藏状态向量会被送入一个特殊的线性层 (Linear Layer),也称为投影层 (Projection Layer)。
这个线性层的输入是隐藏状态向量。
它的输出是一个新的向量,这个向量的维度等于模型词汇表的大小(也就是模型能输出的所有不同 token 的数量)。
这个输出向量中的每个数值被称为一个 logit。每个 logit 对应着词汇表中的一个特定 token。logit 的值越大,表示模型认为该 token 越有可能成为下一个词。
4. 从 Logits 到概率 (Softmax 函数)
Logits 并不是概率,它们的范围可以是任意实数(正数、负数、零)。为了将它们转换成有效的概率分布,使用 Softmax 函数。
Softmax 函数的作用是:
将所有 logit 值转换为正数。
对这些正数进行归一化,使得它们加起来的总和为 1。
经过 Softmax 函数处理后,我们得到一个 概率分布 (Probability Distribution)。这个分布中的每个数值都代表了词汇表中对应 token 作为下一个词的预测概率。
5. 计算交叉熵损失
现在我们有了模型预测的概率分布(例如,对于“Sacramento”的概率是 0.12,对于“SF”的概率是 0.68)。
有了真实的、正确的标签(目标输出,例如,对于“Sacramento”是 1,对于其他是 0)。
交叉熵损失 就是通过比较这个预测的概率分布和真实的标签分布来计算的。
具体来说,只关注正确 token 的预测概率 P
Token 到 Embedding 的转换:
Transformer 模型首先会将输入的文本(经过分词后)转换为 token ID。
然后,这些 token ID 会通过一个 嵌入层 (Embedding Layer) 转换为高维的向量表示,即 token embeddings。此外,Transformer 还会加入 位置编码 (Positional Encoding),因为 Transformer 自身不包含序列的顺序信息。
这些 embeddings 构成了 Transformer 模型的初始输入。
模型内部处理(隐藏状态):
Transformer 的核心是其由多个 自注意力层 (Self-Attention Layers) 和 前馈网络 (Feed-Forward Networks) 组成的编码器(Encoder)和/或解码器(Decoder)堆栈。
在这些层中,模型会不断地处理和转换这些 embeddings,生成包含丰富上下文信息的 隐藏状态 (Hidden States)。每个 token 的隐藏状态都反映了模型对该 token 在整个序列中的理解。
从隐藏状态到 Logits 的线性层:
在 Transformer 的解码器部分(负责生成文本),最后一个解码器层的输出,即最终的隐藏状态向量,会通过一个 线性层 (Linear Layer)。
这个线性层将隐藏状态向量映射到一个维度等于模型词汇表大小的向量,这个向量就是 logits。每个 logit 值代表了词汇表中对应 token 的“得分”。
Softmax 函数生成概率分布:
为了将这些 logits 转换为有效的概率分布,Softmax 函数被应用到 logits 向量上。
Softmax 函数将每个 token 的 logit 转换为一个介于 0 和 1 之间的概率值,并且所有 token 的概率之和为 1。这就是“Predicted tokens distribution”(预测词元分布)。
交叉熵损失进行训练:
一旦模型预测的概率分布和真实的目标输出 (Target Output)(通常是下一个词或被遮蔽的词),就可以计算交叉熵损失 (Cross-Entropy Loss)。
Transformer 模型通过最小化这个交叉熵损失来更新其所有参数(包括 embedding、自注意力权重、前馈网络权重和最终线性层的权重),从而学习如何更准确地预测正确的 token。
从 Token 到 Embedding 的转换、模型内部处理、从隐藏状态到 Logits 的线性层、Softmax 函数生成概率分布,到最终计算交叉熵损失 的完整流程,是 LLM 进行 所有基于梯度下降的训练 的基础机制。
那么,为什么又说 LLM 的预训练是“自监督/无监督”,而微调可以是“有监督”?从数学和计算的角度来看,模型通过比较其预测(概率分布)与一个“真实目标”(独热编码)来计算损失,并根据这个损失来更新自身参数时,这个过程本质上是 “监督式” 的。因为需要一个“标准答案”来指导模型学习。
这里的关键在于:这个答案是从哪里来的
1. 预训练阶段:自监督学习 (Self-Supervised Learning)
定义: 自监督学习是一种特殊的无监督学习,它通过设计一种“前置任务”(pretext task),使得模型能够从 无标签的原始数据本身 自动生成“伪标签”(pseudo-labels)。然后,模型利用这些伪标签进行“监督式”的学习。
“标准答案”的来源: 在预训练阶段,LLM 面对的是海量的原始文本数据,这些数据并没有经过人工标注。
例如: 在“预测下一个词元”任务中(如 GPT 模型),模型看到 “The quick brown fox”,它的任务是预测下一个词。这个“下一个词”——“jumps”——并不是人工标注的,而是原始文本中自然存在的。模型将 “jumps” 视为当前输入 “The quick brown fox” 的“真实目标输出”。
例如: 在“掩码语言模型”任务中(如 BERT 模型),模型看到 “The quick [MASK] fox”,它的任务是预测被遮盖的词。这个被遮盖的词——“brown”——也是原始文本中自然存在的。模型将 “brown” 视为当前输入 “The quick [MASK] fox” 的“真实目标输出”。
为什么是“无监督”? 因为我们不需要人类专家去手动创建这些“输入-目标输出”对。数据本身就包含了这些信息,模型可以自动从中提取出训练信号。这使得模型能够利用几乎无限的文本数据进行学习,从而掌握语言的深层结构和世界知识。
2. 微调阶段:监督学习 (Supervised Fine-tuning, SFT)
定义: 监督学习从带有明确标签的数据中学习。对于每一个输入,我们都预先知道它对应的正确输出是什么,并且这些标签通常是 人工创建或验证的。
“标准答案”的来源: 在微调阶段,我们通常会使用相对较小但高质量的、人工标注的 数据集来训练预训练好的 LLM。
例如: 如果我们想让 LLM 学习如何更好地回答用户指令,我们会收集大量的“用户指令-期望的正确回答”对。当用户输入 “请总结这篇新闻。” 时,模型的目标输出是经过人工精炼的 “这篇新闻主要讲了……”。
例如: 在情感分析任务中,输入 “这部电影太棒了!”,目标输出是 “积极”。这个 “积极” 是由人类标注者提供的。
为什么是“有监督”? 因为“标准答案”是外部提供的,是人类根据特定任务的定义,对数据进行明确标记的结果。模型需要“监督”这些人工标签来学习如何完成特定任务。
总结分析:LLM 训练的混合范式
LLM 的训练是一个多阶段的过程,巧妙地结合了这两种学习范式:
预训练(主要通过自监督学习): 这是一个大规模的、数据驱动的阶段。模型通过在海量无标签文本上执行自监督任务(如预测下一个词或填充空白),来学习语言的通用模式、语法、语义和世界知识。在这个阶段,我描述的“输入 -> 损失计算”的流程中的“真实目标输出”是 从原始数据中自动生成的伪标签。
微调(通常涉及监督学习): 在预训练之后,模型已经具备了强大的语言能力。为了让模型更好地适应特定任务或用户需求,我们会进行微调。在这个阶段,我描述的“输入 -> 损失计算”的流程中的“真实目标输出”是 人工标注的、针对特定任务的真实标签。
- 无监督、自监督、监督
纯无监督学习: 模型在训练过程中,完全不依赖任何形式的标签或目标输出。它的目标是发现数据中固有的结构、模式或隐藏的表示。这类学习的典型例子包括:
聚类 (Clustering): 例如 K-Means 算法,它将相似的数据点分组,而无需预先知道这些组是什么。模型只是根据数据点之间的距离或相似性来划分它们。
降维 (Dimensionality Reduction): 例如 PCA (主成分分析) 或 t-SNE,它们旨在减少数据的维度,同时保留数据中最重要的信息,以便于可视化或进一步分析。模型只是寻找数据中方差最大的方向或保持局部结构。
关联规则学习 (Association Rule Learning): 例如 Apriori 算法,用于发现数据集中项之间的有趣关系(如“购买牛奶的人也经常购买面包”),同样不需要任何标签。
自监督学习 (LLM 预训练): 从无标签数据中自动生成伪标签,然后用这些伪标签进行监督学习。它利用了数据本身的结构作为监督信号。
监督学习 (LLM 微调): 使用人工标注的标签进行学习。
对于绝大多数深度学习模型,无论是传统的 CNN、RNN 还是生成式的 Transformer 大模型,当它们进行“训练”和“学习”时,都离不开某种形式的“目标信号”来指导它们的参数更新。它们都需要一个“标准答案”来计算损失,然后根据这个损失来调整内部的权重。因此,严格意义上的“纯无监督学习”在深度学习中是相对较少见的,或者说,它的定义和应用场景与我们通常理解的“训练模型来预测或生成特定内容”有所不同。
为什么大多数深度学习训练不是“纯无监督”?
1.梯度下降的本质: 深度学习模型的核心训练机制是 梯度下降 (Gradient Descent)。要进行梯度下降,就需要计算一个 损失函数 (Loss Function)。损失函数衡量的是模型的预测与“真实目标”之间的差距。没有“真实目标”,就无法计算损失,也就无法通过梯度下降来优化模型参数。
2.“纯无监督”的定义: 纯无监督学习的目标是发现数据中固有的结构、模式或隐藏的表示,而 完全不依赖任何形式的标签或目标输出。
传统深度学习模型 (CNN, RNN)
CNN (卷积神经网络):
图像分类、目标检测: 几乎都是 监督学习。例如,给一张猫的图片,模型需要预测“猫”这个标签;给一张图片,模型需要框出汽车并识别为“汽车”。这些“猫”、“汽车”的标签都是人工标注的。
自监督应用: 也有一些自监督的 CNN 应用,例如通过旋转图片让模型预测旋转角度,或者通过预测图片中被遮盖的补丁。但这些仍然是设计了一个“前置任务”来生成“伪标签”。
RNN (循环神经网络):
情感分析、机器翻译: 几乎都是 监督学习。例如,输入一句英文“Hello”,模型目标输出是法文“Bonjour”;输入一段文字,模型目标输出是“积极”或“消极”。这些目标输出都是人工提供的。
语言建模 (Language Modeling): 这是 RNN 和 Transformer 预训练的基础。模型预测序列中的下一个词。虽然这个“下一个词”是数据本身提供的,但它仍然是一个明确的预测目标,模型会根据它计算损失。所以,这属于 自监督学习。
生成式 Transformer 大模型 (LLMs)
预训练阶段: LLM 的预训练是典型的自监督学习。无论是“下一个词元预测”还是“掩码语言模型”,模型都有一个明确的“伪标签”作为目标。这个伪标签是数据本身提供的,但它仍然是模型计算损失和优化参数的依据。
微调阶段: 通常是 监督学习 (SFT) 或 强化学习与人类反馈 (RLHF),这些都涉及明确的标签或反馈信号。
深度学习中“纯无监督”的例子
通常不涉及“预测下一个词”或“分类”这样的任务,而是更侧重于 表示学习 (Representation Learning) 或 数据生成 (Generative Modeling),且其损失函数的设计不依赖于外部标签或数据内部的直接预测目标。一些接近“纯无监督”的深度学习例子:
自编码器 (Autoencoders, AEs) 和变分自编码器 (Variational Autoencoders, VAEs):
目标: 学习数据的压缩表示(编码),并能从这个表示中重建原始数据(解码)。
“标签”: 模型的输入就是它的目标输出。模型尝试最小化重建误差(即输入和输出之间的差异)。虽然有损失函数,但它没有外部标签,也没有从数据中“生成”伪标签,而是直接使用输入本身作为监督信号。这被广泛认为是无监督学习的一种形式,因为它主要用于学习数据的内在结构和表示。
生成对抗网络 (Generative Adversarial Networks, GANs):
目标: 训练一个生成器来生成逼真的数据,并训练一个判别器来区分真实数据和生成数据。
“标签”: 判别器有“真实”和“虚假”的标签,但这些标签是内部生成的,而不是外部人工标注的。生成器没有明确的“正确输出”标签,它的目标是让判别器无法区分其生成的数据。GANs 可以在没有标签的数据集上生成新的、逼真的数据,因此常被归类为无监督或半监督生成模型。
深度聚类 (Deep Clustering):
目标: 利用深度学习模型来学习数据的特征表示,然后在此基础上进行聚类。
“标签”: 聚类本身就是无监督的,模型会尝试将相似的数据点分组,而无需预先知道这些组是什么。深度学习部分可能通过自编码器等方式学习表示。
总结:
“训练”和“学习”在深度学习中几乎总是意味着有某种形式的“目标信号”来计算损失并优化模型参数。
纯无监督学习 在深度学习中更侧重于 学习数据的内在结构、表示或生成数据,而不依赖于外部标签或从数据中直接提取的预测目标。
自监督学习(LLM 预训练的核心)是介于两者之间的一种强大范式:它巧妙地利用无标签数据本身来生成“伪标签”,从而进行监督式的训练。
- hyperparameters tuning
Learning Rate / Epoch / Batch size / random seeds…dark arts
2.3.8 Parameter efficient fine-tuning (PEFT, eg. LoRA)
在对模型进行微调时,权重的更新往往存在大量的冗余(redundancy)。在大型语言模型(LLMs)等现代深度学习模型中,W 和 ΔW 矩阵可以非常庞大,包含数十亿甚至数万亿的参数。如果每次微调都需要存储和更新整个 ΔW 矩阵,会带来巨大的计算和存储开销。
可以利用 ΔW 中的冗余性,找到一种更高效、参数更少的方法来表示这些权重更新。例如,低秩适应(Low-Rank Adaptation, LoRA)等参数高效微调(PEFT)技术就是基于这个思想。它们不直接学习整个 ΔW,而是学习两个小得多的矩阵,这两个小矩阵的乘积可以近似地表示 ΔW,从而大大减少了需要训练的参数量和计算资源。
核心思想:大矩阵变小矩阵。可以把一个矩阵看作是某种信息或数据的集合。在数学中,矩阵的“秩”可以理解为它所包含的线性独立信息的维度。一个低秩矩阵意味着它的信息可以通过更少的维度来表示,或者说它包含大量的冗余信息。矩阵可以进行低秩分解的核心原因在于:许多真实世界的数据和矩阵本身就是低秩的,或者可以被一个低秩矩阵很好地近似。最强大的低秩分解工具是奇异值分解 (Singular Value Decomposition, SVD)。
在大型预训练模型进行微调时,发现模型权重的变化量 ΔW (即微调前后权重的差异) 往往是低秩的。这意味着,虽然 ΔW 矩阵可能非常庞大,但它所包含的有效信息(即为了适应新任务所需进行的调整)实际上只集中在少数几个重要的方向上。大部分参数的变化是相互关联的,或者对最终性能影响很小。这种现象是经验观察到的,也是LoRA等方法的基础。
LoRA(Low-Rank Adaptation)的核心思想就是利用 ΔW 的这种低秩特性。它不直接学习和更新整个庞大的 ΔW 矩阵,而是引入两个小得多的矩阵 A 和 B,并将 ΔW 近似为它们的乘积。
训练的参数少:只训练 A 和 B 这 4000 个参数。
影响的参数多:通过矩阵乘法 AB,这 4000 个参数共同决定了 1,000,000 个元素的 ΔW 矩阵。
最终修改:这个 ΔW 矩阵的 1,000,000 个元素会逐一加到原始大模型 W 的 1,000,000 个参数上,从而实现了对所有参数的间接调整。
2.3.9 RL: Reward and preference, Value Function/Model
- Preference Learning, Reward Model:
偏好学习的核心就是训练一个“奖励模型”(Reward Model,RM)。 Reward Model = Preference Model
数据收集
首先,AI模型会生成一些候选的响应或输出。
然后,人类评估者会对这些输出进行比较,给出偏好判断。例如,他们可能会看到两个AI生成的回答A和B,然后选择“A更好”或者“B更好”(或者“一样好”)。这些成对的偏好数据就是训练奖励模型的输入。
模型架构
奖励模型本身通常是一个神经网络(例如,一个大型语言模型,但它的输出层被修改为输出一个标量分数)。它会接收AI生成的文本、图像或其他数据作为输入。
损失函数与优化
对于每一对人类偏好的数据(例如,人类认为A优于B),奖励模型会预测A的奖励分数 r(A) 和B的奖励分数 r(B)。
然后,它会计算一个差异 r(A)−r(B)。
接着,就像图中所示,这个差异会通过一个Sigmoid函数转换为一个概率,表示模型认为A优于B的可能性。
训练的目标是最小化一个损失函数,这个损失函数会惩罚模型预测的概率与人类真实偏好不一致的情况。例如,如果人类说A优于B,但模型预测A优于B的概率很低,那么损失就会很大。
通过不断调整奖励模型的内部参数,使其预测的偏好概率与人类的真实偏好尽可能一致,模型就学会了如何评估和打分。
强化学习(Reinforcement Learning,RL): 在像“基于人类反馈的强化学习”(Reinforcement Learning from Human Feedback, RLHF)这样的方法中,训练好的奖励模型会取代传统的、手动设计的奖励函数。它会为AI模型的行为提供实时的奖励信号,指导AI模型学习生成更受人类偏好的输出。
评估: 它可以作为一个自动评估工具,来衡量不同AI模型或不同输出的质量,而无需每次都进行人工评估。
reward model训练数据案例:
奖励模型(Reward Model, RM)的训练数据通常都是基于人类偏好标签(Human Preference Labels)的
Prompt (P): "写一个关于勇敢老鼠的短故事。"
Response_1 (R_A): "一只名叫Tiny的老鼠拯救了世界。"
Response_2 (R_B): "在舒适的洞穴里住着Remy,一只充满勇气的老鼠。在一个暴风雨的夜晚,他冒险出去营救被困的朋友,面对着一只暴躁的猫和一个险恶的水坑。"
人类偏好: R_B 优于 R_A
{
"Input": {
"Prompt": "写一个关于勇敢老鼠的短故事。",
"Response_1": "一只名叫Tiny的老鼠拯救了世界。",
"Response_2": "在舒适的洞穴里住着Remy,一只充满勇气的老鼠。在一个暴风雨的夜晚,他冒险出去营救被困的朋友,面对着一只暴躁的猫和一个险恶的水坑。"
},
"Target_Output": 0 // 表示 Response_2 (Remy的故事) 优于 Response_1 (Tiny的故事)
}
Prompt (P): "推荐一部科幻电影。"
Response_1 (R_A): "我推荐《银翼杀手2049》,它在视觉效果、叙事深度和哲学探讨方面都非常出色,是一部值得反复品味的电影。"
Response_2 (R_B): "科幻电影有很多,比如《星球大战》或者《阿凡达》,你可以看看。"
人类偏好: R_A 优于 R_B
{
"Input": {
"Prompt": "推荐一部科幻电影。",
"Response_1": "我推荐《银翼杀手2049》,它在视觉效果、叙事深度和哲学探讨方面都非常出色,是一部值得反复品味的电影。",
"Response_2": "科幻电影有很多,比如《星球大战》或者《阿凡达》,你可以看看。"
},
"Target_Output": 1 // 表示 Response_1 (银翼杀手2049) 优于 Response_2 (泛泛推荐)
}
不是一次性将 prompt + response_1, prompt + response_2, target 作为模型输入
而是:
模型本身会进行两次独立的调用:一次处理 (Prompt + Response_1) 得到 score_1,另一次处理 (Prompt + Response_2) 得到 score_2。即奖励模型会分别对这两个序列进行前向传播
训练框架(损失函数)会利用这 score_1、score_2 和 Target_Output 来计算损失,从而指导模型的学习。一个常见的损失函数是 pairwise ranking loss,它通常基于 score_2 - score_1 (或 score_1 - score_2) 的差值来计算。
- Reward Model 和 Verifiers
在强化学习阶段,RM和VR的信号会被结合起来,形成一个综合奖励(Composite Reward),然后这个综合奖励被反馈给正在学习的LLM。(最常见的方式是加权求和)
这个 Reward total 就是LLM在当前生成行为中获得的奖励。强化学习算法(如PPO, Proximal Policy Optimization)会利用这个奖励信号来更新LLM的参数。
- Value Function/Model
(训练一个网络是PPO的方式去弄Value Function)
价值函数网络(Value Function Network)和最终要进行RLHF的大模型(即策略网络,Policy Network)是同步且迭代地训练的。它们是PPO算法的两个核心组成部分,通过一个精巧的循环机制,相互提供信息,共同推动模型向生成更符合人类偏好的文本方向发展。价值网络提供了一个稳定的基线,帮助策略网络更有效地利用奖励信号,而策略网络的行动又为价值网络提供了新的学习数据。
输入: (Prompt, Partial_Response) (即当前状态)。
输出: 标量值(代表预测的未来累积奖励)。
训练数据: LLM与环境交互后计算出的实际累积回报G。
作用: 提供基线用于计算优势函数,稳定策略训练。
2.3.10 RL: Training objective and RLHF
强化学习(Reinforcement Learning, RL)训练大语言模型(LLM)通常会分为两个主要阶段:
阶段一:数据收集 {输入, 输出, 奖励} (Phase 1: Collect data {input, output, reward})
LLM 生成响应 (LLM generates a dataset of rollouts)
一个带有“LLM”标志的机器人接收一个“Prompt”(提示)。
然后,它生成一个“Answer”(回答)。
这个过程被称为“rollout”,就像在玩一个游戏,模型根据当前状态(提示)采取行动(生成回答)。
应用奖励获得轨迹 (Apply rewards to get trajectories)
LLM生成的“Answer”随后会被我们的奖励机制(之前讨论过的奖励模型RM和验证器VR的组合)进行评估。
评估结果会生成一个“Reward”(奖励)。
这样,就得到了一个完整的“轨迹”(trajectory),它包含了:{Prompt, Answer, Reward}。
这个阶段会重复多次,生成大量的 (Prompt, Answer, Reward) 数据对,形成一个数据集。
阶段二:在轨迹上进行训练 (Phase 2: Train on trajectories)
LLM会利用这些轨迹数据来更新其内部参数,通过强化学习算法来实现更新LLM的内部参数.
目标是最大化它所获得的奖励 ∑(token-indices) [prob of next token ∗ how good token was] .希望LLM去最大化的期望奖励。简单来说,希望模型学会生成那些能带来高奖励的词元序列。
Tips: 只有分数是无法更新参数的,无法训练学习,因为,一是这个分数无法梯度,二是我们的目标是上面那个求和;因此,需要通过强化学习算法来实现更新LLM的内部参数。
∑(token-indices) [prob of next token] ∗ [how good token was]
| |
LLM / LLM(ref) r-b
- ∑token-indices
表示对生成文本中的每一个词元(token)进行求和。在LLM生成文本时,它会一个接一个地生成词元,这个目标函数就是对每个生成步骤的贡献进行累加。
- [prob of next token]
这部分衡量的是当前模型生成某个词元的概率与一个参考模型生成该词元的概率之间的比值。
(针对单个词元,是LLM在给定当前上下文(提示和前面已生成的词元)时,生成某个特定词元yi的概率)
例子
假设LLM在给定提示后,生成了序列 "Paris is the capital of France."。
在生成第一个词元时,模型可能计算出 "Paris" 的概率是 0.8,"London" 的概率是 0.1,"Rome" 的概率是 0.05 等等。如果模型实际生成了 "Paris",那么我们关心的 prob of next token 就是 P("Paris"∣prompt)。
在生成第二个词元时(给定 "Paris"),模型可能计算出 "is" 的概率是 0.9,"has" 的概率是 0.05 等等。如果模型实际生成了 "is",那么我们关心的 prob of next token 就是 P("is"∣prompt, "Paris")。
所以,prob of next token 并不是指在某个生成步骤中,所有可能词元的概率分布,而是特指在一次具体的生成(rollout)中,模型为它实际选择并输出的那个词元所赋予的概率。
这里的“比”是一个非常重要的概念,称之为策略比率(Policy Ratio),或者在某些上下文中也叫重要性采样比率(Importance Sampling Ratio)。
LLM是当前策略(Current Policy),也就是我们正在训练和优化的LLM。它代表了模型在给定上下文后,生成特定词元yi的概率。
LLM(ref)是参考策略(Reference Policy)。通常,它是我们开始RL训练时的原始LLM,或者是一个稍旧的策略版本。它的参数是固定的,不会随着训练而更新。
衡量策略变化程度: 这个比率告诉我们,当前策略生成某个词元yi的概率,相对于参考策略生成该词元的概率,是增加了还是减少了,增加了多少倍或减少了多少倍。
如果比率 大于 1,说明当前策略更倾向于生成这个词元了。
如果比率 小于 1,说明当前策略不太倾向于生成这个词元了。
如果比率 等于 1,说明两个策略生成该词元的概率相同。
在PPO算法中,这个比率被用来限制策略更新的幅度。PPO会“裁剪”(clip)这个比率,确保新策略不会离旧策略太远。
- [how good token was]
r-b,这里的“减”构建了一个非常重要的概念,称之为优势函数(Advantage Function)。r是奖励,b是基线baseline通常是价值函数估计
代表的是整个生成的序列(即LLM的完整回答)所获得的最终奖励, 不是指单个词元本身的好坏。这个奖励是由奖励模型(RM)和验证器(VR)综合评估后给出的一个分数。
在强化学习中,通常是整个序列(从第一个词元到最后一个词元)作为一个“行为”或“轨迹”,获得一个单一的奖励信号。这个奖励信号会被“归因”到序列中的每一个决策(生成每一个词元)
减少方差,稳定训练: 在RL中,奖励信号往往非常稀疏且波动性大。直接使用原始奖励来更新策略会导致训练过程不稳定,收敛困难。通过从实际奖励中减去一个基线(即平均预期奖励),我们得到的是优势(Advantage)。
价值函数是一个独立的神经网络,它通过监督学习的方式进行训练。它的“标签”数据不是人类提供的,而是通过LLM与环境交互(生成文本并获得奖励)后,计算得到的实际累积回报。它的训练目标是预测从当前状态开始的未来总奖励,从而为策略网络的训练提供一个稳定的基线,帮助计算优势函数,最终实现更高效、更稳定的策略优化
- 为什么是 [prob of next token] * [how good token was] (概率乘以奖励)
如果LLM生成了一个词元,并且这个词元所在的整个序列获得了高奖励,那么我们希望下次再遇到类似情况时,LLM能更有可能生成这个词元。所以,我们要提高 prob of next token。
如果LLM生成了一个词元,但这个词元所在的整个序列获得了低奖励(甚至惩罚),那么我们希望下次再遇到类似情况时,LLM能更不可能生成这个词元。所以,我们要降低 prob of next token。
prob of next token * how good token was 这个乘积,正是将“模型当前生成某个词元的倾向性”与“这个倾向性带来的结果好坏”结合起来。
如果 how good token was 是一个正值且大(好结果),那么这个乘积就大,会鼓励模型增加生成该词元的概率。
如果 how good token was 是一个负值或小(坏结果),那么这个乘积就小(甚至负),会鼓励模型减少生成该词元的概率。
- 为什么 ∑token-indices(对所有词元求和)
在强化学习中,LLM生成一个回答序列可以被看作是一系列连续的决策过程:模型在给定上下文后,选择生成第一个词元;然后基于新的上下文(包含第一个词元),选择生成第二个词元;依此类推,直到生成整个序列。
一个回答由多个词元组成: LLM的输出是一个序列,由多个词元 y1, y2,…,yL构成。
每个词元都贡献了最终奖励: 整个序列的奖励是所有词元共同作用的结果。虽然奖励信号可能是一个单一的数值(针对整个序列),但在训练时,我们需要将这个奖励信号“传播”回序列中的每一个词元,以便模型知道在每个生成步骤中应该如何调整。将整体奖励“广播”到每个决策点
求和表示整个序列的期望: 对所有词元位置上的 [prob of next token * how good token was] 求和,代表了我们希望最大化的整个序列的累积期望奖励。它确保了模型在生成整个回答的过程中,每一步都朝着最大化最终奖励的方向努力。
将总奖励归因到局部决策: 尽管奖励是针对整个序列的,但需要知道是序列中的哪些“决策”(生成哪些词元)导致了高奖励,哪些导致了低奖励。通过将总奖励乘以每个实际生成词元的概率,我们间接地将这个总奖励的“影响”传播给了序列中的每一个词元。
指导模型更新:
如果一个序列获得了高奖励 ($R_{\text{total}}$ 为正且大),那么这个乘积项就会为正且大。在优化时,模型会调整参数,增加生成该序列中所有词元的概率。
如果一个序列获得了低奖励 ($R_{\text{total}}$ 为负或小),那么这个乘积项就会为负或小。在优化时,模型会调整参数,减少生成该序列中所有词元的概率。
数学原因:R_total 是一个“黑箱”结果,不是可微分的函数,无法直接计算 R total对生成模型(LLM)参数的梯度。log P_policy(sequence) 提供了可微分的路径。
- 为什么不能直接用“生成下一个词元的概率”乘以“原始奖励”?(简单策略梯度的局限)
高方差…等等问题
比解决:数据效率和策略更新稳定性…
实现离策略学习(Off-policy Learning)和经验重用:
LLM_ref 是一个旧的策略版本(通常是训练前的原始模型或上一个迭代的策略)。
这个比率允许我们使用旧策略(LLM_ref)生成的数据来训练新策略(LLM)。这被称为“重要性采样(Importance Sampling)”。它通过调整旧策略生成的数据的权重,使其看起来像是新策略生成的数据。这样,我们就不必在每次微小更新后都重新生成大量文本,大大提高了训练效率。
限制策略更新幅度,确保稳定性(PPO的核心):
在PPO中,这个比率会被“裁剪”(clipped)。这意味着如果新策略生成某个词元的概率比旧策略高太多或低太多,这个比率就会被限制在一个范围内(例如,0.8到1.2之间)。
这样做的目的是防止新策略在一次更新中偏离旧策略太远。如果策略变化过大,可能导致模型“忘记”如何生成流畅、合理的文本,或者陷入局部最优。通过限制比率,PPO确保了训练的稳定性和模型的持续改进,而不是剧烈波动。
作为“锚点”,防止模型遗忘(ref 的作用):
LLM_ref 作为“参考模型”,在RLHF中尤其重要。它代表了模型在RL训练前所具备的语言能力。通过与LLM_ref进行比较,我们确保模型在学习人类偏好的同时,不会牺牲其基本的语言生成质量(如语法、流畅性)。这通常通过一个KL散度惩罚项来实现,而这个比率是计算KL散度的关键组成部分。
减解决:高方差和绝对奖励…
2.3.11 RL: PPO and GRPO Algorithms, and DPO Algorithms
- PPO, Proximal Policy Optimization
PPO是目前在强化学习领域,尤其是在RLHF中,应用最广泛、效果最好的策略梯度算法之一。它的核心目标是在更新策略(LLM)时,既要让策略朝着能获得更高奖励的方向改进,又要避免策略更新过大导致训练不稳定或偏离太远。
三大核心:
策略网络 (Policy Network/Model):这就是我们的LLM,它负责生成文本(采取动作)。
价值网络 (Value Network/Model):它预测从当前状态开始的未来累积奖励。
奖励模型 (Reward Model):提供即时奖励信号。
在RLHF中,PPO是连接奖励模型和LLM(策略网络)的桥梁。奖励模型提供PPO所需的即时奖励,价值函数提供PPO所需的基线,PPO利用这些信号,通过其独特的裁剪机制,稳定且高效地更新LLM的参数,使其生成更符合人类偏好的文本。
- GRPO, Group Relative Policy Optimization (DeepSeek)
DeepSeekMath 论文中描述的 GRPO 是一个PPO的变体,它对PPO算法中的一个核心组件——价值函数(Critic Model)进行了创新性的处理。
放弃了独立的 Critic Model (价值函数网络):这与标准 PPO 不同,标准 PPO 需要一个额外的神经网络来预测状态价值。
通过“组分数”(Group Scores)来估计基线 (Baseline):这是其资源高效的关键所在。它不再需要训练一个额外的网络,而是从一组生成结果的分数中推断出基线。
显著减少了训练资源:这是这种方法的主要优势,对于训练大型模型来说非常重要。
DeepSeek GRPO 的核心思想:用“组分数”替代独立价值函数
“Sample many outputs per input.” (为每个输入采样多个输出)
你给LLM一个Prompt(例如:“请写一个关于月球的短故事。”)。
在标准的PPO中,LLM会生成一个响应。但在GRPO中,LLM会为这个同一个Prompt生成多个不同的响应。
图中的红线从文字指向了上方和下方的数轴,表示这些输出都源自同一个输入。
“These are called a 'group'.” (这些被称为一个“组”)
由同一个Prompt生成的这些多个响应,构成了一个“组”。
图中的小圆点(棕色和红色)代表了这些不同输出所获得的奖励分数。例如,对于一个Prompt,模型可能生成了5个不同的故事,每个故事都由奖励模型给出了一个分数。
“Average the rewards in a group.” (对组内的奖励进行平均)
这是关键一步!GRPO不对每个状态预测一个未来价值,而是计算这个“组”内所有输出的平均奖励。
在图的上半部分,数轴上散布着代表奖励的圆点(例如,在3到6之间)。红色的垂直线就代表了这些奖励的平均值(例如,大约在5附近)。这个平均值就是GRPO为这个特定输入(Prompt)动态估计的基线。
“Subtract baseline” (减去基线)
一旦我们有了这个“组平均奖励”作为基线,我们就可以将组内每个输出的奖励减去这个基线。
图的下半部分展示了这一操作的结果。原本散布在5左右的奖励点,在减去平均值(5)之后,就围绕着0重新分布了(例如,从-2到2)。红色的垂直线现在位于0,表示新的平均值是0。
为什么这样做是有效的
基线的作用:在强化学习中,基线的主要作用是减少策略梯度估计的方差,从而稳定训练。优势函数 A=实际回报−基线 告诉我们,某个动作(或输出)比平均情况“好多少”或“差多少”。一个好的基线可以确保优势函数既有正值也有负值,从而让模型知道哪些行为应该被鼓励,哪些应该被抑制。
GRPO的创新:
无需独立Critic:标准PPO需要一个额外的神经网络(Critic)来学习预测 V(s) 作为基线。这个Critic需要额外的参数、计算资源和训练时间。
经验性基线:GRPO通过经验性地计算同一个输入下多个输出的平均奖励来获得基线。这个平均值自然地反映了当前策略在给定输入下,通常能获得的奖励水平。
相对优势:当每个输出的奖励减去这个“组平均奖励”时,我们得到的是相对优势。这意味着我们不再关心一个输出的绝对好坏,而是它相对于同一Prompt下其他可能输出的好坏。这同样能提供一个有用的信号来指导策略更新。如果一个输出的奖励高于平均值,它的相对优势就为正,策略就会被鼓励;反之则被抑制。
资源效率:通过这种方式,DeepSeek的GRPO成功地避免了训练一个额外的价值网络,从而显著减少了训练所需的计算资源和内存,这对于训练大型语言模型来说是一个巨大的优势。
- DPO, Direct Preference Optimization
DPO 是一种直接从人类偏好数据中优化语言模型的方法,它最大的特点是,不需要显式地训练一个奖励模型(Reward Model)或一个独立的价值函数(Value Function)。它直接利用一个直接的损失函数来优化策略模型。
DPO 的核心是人类偏好数据,这种数据通常以成对比较的形式存在。对于同一个 Prompt,人类标注者会选择一个他们更喜欢的 Response (称为 preferred 或 chosen),并指出一个他们不喜欢或认为较差的 Response (称为 rejected 或 un_chosen)。
DPO 的强化学习体现在它直接优化了策略模型(LLM)的行为,使其能够最大化一个从人类偏好数据中推断出来的“隐式奖励”。尽管没有显式的奖励模型,但 DPO 的损失函数实际上是在引导 LLM 学习一个“行为模式”。
性能媲美 PPO, 在许多任务上,DPO 能够达到甚至超越 PPO 的对齐效果。虽然 DPO 效率高,但足够数量的高质量偏好对 (prompt, chosen, rejected) 仍然至关重要。超参数调整等等也很重要。
[
{
"prompt": "写一个关于勇敢老鼠的短故事。",
"chosen": "在舒适的洞穴里住着Remy,一只充满勇气的老鼠。在一个暴风雨的夜晚,他冒险出去营救被困的朋友,面对着一只暴躁的猫和一个险恶的水坑。",
"rejected": "一只名叫Tiny的老鼠拯救了世界。"
},
{
"prompt": "如何制作一杯完美的卡布奇诺?",
"chosen": "制作一杯完美的卡布奇诺需要精确的比例和技巧:首先,萃取一份浓郁的意式浓缩咖啡;然后,将新鲜牛奶打发至细腻、有光泽的奶泡,温度控制在60-65°C;最后,将奶泡倒入浓缩咖啡中,形成经典的奶咖分层和漂亮的拉花。",
"rejected": "先煮咖啡,再加牛奶,然后弄点泡泡就行了。"
}
]
2.3. LLM训练过程 vs 我的语音增强深度学习模型 (主要步骤概括)
- LLM:
1.数据准备 Data Preparation:数据收集(文本数据,数据量通常达到数万亿个token) -> 数据清洗(去重,归一化,敏感信息处理等)-> 分词(Tokenization)
2.预训练 Pre-training:模型选择(Transformer-based模型,典型的如BERT)-> 自监督学习(Next Token Prediction | Masked Language ModelingMLM的BERT)
3.监督微调 Supervised Fine-tuning SFT:有监督学习(高质量的指令-相应对。 将预训练模型加载进来,然后使用这个指令-响应数据集进行标准的监督学习。模型的目标是,给定一个指令作为输入,生成一个与数据集中对应的响应作为输出。)
4.对齐 / 基于人类反馈的强化学习 (Alignment / RLHF, Reinforcement Learning from Human Feedback)
目的: 进一步将模型与人类的偏好、价值观和行为准则对齐,使其输出更符合“有益、无害、真实”的原则。
如何工作 (以 RLHF 为例): RLHF 通常包含三个子阶段:
1.收集对比数据与训练奖励模型 (Reward Model, RM):
对于同一个指令,让 SFT 模型生成多个不同的响应。
人类标注员对这些响应进行排序或打分,表示他们更喜欢哪个响应。
使用这些人类偏好数据来训练一个单独的奖励模型 (Reward Model, RM)。这个 RM 的目标是学习人类的偏好,能够预测给定响应的“好坏”分数。
2.基于奖励模型的强化学习 (Reinforcement Learning):
使用 SFT 过的模型作为策略模型 (Policy Model)。
奖励模型作为奖励函数,为策略模型生成的新响应提供奖励信号。
通过强化学习算法(如 PPO, Proximal Policy Optimization),微调策略模型。模型的目标是生成能够最大化奖励模型分数的响应。
在训练过程中,还会引入一个惩罚项(KL 散度),以防止模型偏离 SFT 阶段学到的语言模型能力太远,避免生成不连贯或无意义的文本。
3.迭代优化: 这个过程可以迭代进行,不断收集新的数据,训练更优的奖励模型,并进一步微调策略模型。
其他对齐方法: 除了 RLHF,还有一些更简单的替代方案,如 直接偏好优化 (Direct Preference Optimization, DPO),它直接利用人类偏好数据来优化模型,而不需要显式地训练一个奖励模型,简化了训练流程。
5.部署与推理
Tips (大概):
- 预训练: 计算资源需求最高,通常需要数千甚至数万颗高端 GPU,持续数周到数月。
- RLHF: 计算资源需求次高,尤其是在训练奖励模型和 PPO 阶段,需要同时管理多个 LLM 实例,并且 RL 训练
本身就更复杂。通常也需要数百到数千颗 GPU。
- SFT(全参数微调): 计算资源需求相对较低,但对于大型 LLM 仍需多张高端 GPU。
- SFT(PEFT,Parameter-Efficient Fine-Tuning/量化): 计算资源需求最低,甚至可以在单张消费级 GPU 上进行,使得个人和小型团队也能参与 LLM 微调。
参数高效微调 (PEFT) 技术: 如 LoRA(Low-Rank Adaptation)、QLoRA 等,通过只训练模型的一小部分参数或注入少量可训练参数,可以大幅减少显存和计算需求。QLoRA 甚至可以在单张消费级 GPU 上微调 7B 甚至 13B 模型。
量化技术: 如 INT8 量化、FP16 量化等,通过将模型参数和激活值从 32 位浮点数压缩到 8 位整数或 16 位浮点数,大幅减少模型大小和计算需求。
- LCLED(我的语音增强深度学习模型):
1.数据准备:数据集 -> 训练集,验证集,测试集 -> 特征提取(语音特征提取,含STFT,MFCC,梅尔频谱等运算过程)-> 构建数据对(输入clean语音,输出带噪语音)
2.训练:模型选择(搭建LCLED,设置超参数、损失函数等等,炼丹) -> 监督学习
3.测试:使用测试集评估模型性能,常用指标包括信噪比(SNR)等音频质量指标
4.部署与增强语音
Tips: 实验室单块GPU训练1-2天
3.AI 驱动全栈开发新范式 & AI Native 软件工程
3.1 前端领域
设计稿到前端代码:D2C + MCP + AI
Tricks: 图与图编码
https://mp.weixin.qq.com/s/RBIlsqdkN7CNDuGWxhoxGQ
AI前端开发新范式:业务需求逻辑、交互设计、服务端接口定义即可生成代码
3.2 后端领域
NL2SQL:自然语言到SQL
开源大模型边缘设备部署:qwq, gpt-oss-20b(16G内存)
3.3 前后端IDE
- Cursor
参考(阿里的技术文档还是太棒了):https://mp.weixin.qq.com/s/UM3nBcX6JpYtnchSCdrxOA
还有很多:
# 项目文档规范
**文档受众明确为软件开发人员**,目的是帮助开发团队快速理解系统架构、业务逻辑和技术实现细节,便于代码维护、功能扩展和知识传递。
## 关键规则
- 项目文档必须包含四个核心部分:项目简介、核心领域模型、项目结构和外部依赖
- 接口文档必须按照@接口文档规范进行编写和维护
- 业务流程文档必须按照@业务流程文档规范进行编写和维护
- 文档应保持客观性,基于现有代码而非理想状态
- 文档中使用的术语必须与代码中的术语保持一致
- 文档应使用Markdown格式,支持图表、表格和代码块
- 代码示例必须是从实际代码中提取的,而非虚构的
- 图表应使用Mermaid或PlantUML语法,以确保可维护性
- 文档应当引用具体的代码文件路径,便于读者查找相关实现
- 首先判断项目是否使用GBF框架,并根据实际架构选择适合的文档结构和内容
- 所有文档必须统一放置在docs目录下,并使用规定的中文名称
- **文档生成过程中必须确保完整覆盖所有内容,不允许任何遗漏**
## 文档优化与结构指南
- **主索引文档**:每个核心部分创建一个主索引文档,包含子文档链接和简要说明
- **文档内导航**:超过500行的文档必须在开头提供目录
- **分层结构**:按照"金字塔结构"组织(顶层:核心概念;中层:主要功能模块;底层:具体实现细节)
- **文档拆分**:接口超过20个时按业务域拆分;核心实体超过10个时按业务领域拆分
## 文档结构和内容要求
### 1. 项目简介 - docs/项目概述.md
必须包含:项目背景、项目目标、功能概述、技术栈和架构类型(明确是否使用GBF框架)
### 2. 核心领域模型 - docs/领域模型说明.md
必须包含:
- 领域模型概述:核心业务概念的定义和边界
- 核心实体关系图:使用E-R图或类图表示
- 关键业务场景下的模型交互
- 数据流转关系
**强制性领域模型扫描规则**:
- **全面扫描**:包括`*Entity.java`、`*DO.java`、`*PO.java`、`*Model.java`、`@Entity`、`@Table`、`@Document`注解类、服务层核心模型、DTO/VO类
- **按目录结构识别**:位于`model`、`domain`、`entity`目录及其子目录下的Java类文件,以及领域模型专用包路径(如`*.domain.*`、`*.model.*`、`*.entity.*`)下的类文件
- **完整提取**:实体属性和业务含义、实体关系、聚合结构、生命周期和状态流转
- **识别规则**:属性约束、实体关系约束、状态转换规则
**领域模型分析策略**:
- 全域扫描实体类和值对象,支持多种ORM框架
- 提取关联关系(通过字段类型、泛型参数和ORM注解)
- 识别聚合根和聚合边界(通过包结构和类间关系)
- 分析继承结构(包括抽象类、接口和实现类)
- 提取业务方法和状态转换逻辑
- 生成完整属性表和业务规则说明
**GBF框架项目补充**:扩展点定义与实现、行业/场景定制点、路由条件与动态选择机制
### 3. 接口文档 - docs/接口文档.md
接口文档应遵循专门的@接口文档规范进行创建和维护,以确保API接口的完整记录和更新。
### 4. 业务流程 - docs/业务流程说明.md
业务流程文档应遵循专门的@业务流程文档规范进行创建和维护,以确保业务流程的完整记录和更新。
### 5. 项目结构 - docs/项目结构说明.md
必须包含:项目模块划分、代码组织结构、关键包说明、分层架构说明
**GBF框架项目补充** - docs/GBF框架应用说明.md:
GBF分层结构、扩展点文件位置、行业定制目录、场景定制目录
### 6. 外部依赖与下游服务 - docs/外部依赖说明.md
必须包含:
- 下游服务概述:依赖的所有外部服务列表和用途
- 调用关系图:系统与外部服务的调用关系
## 文档生成工作流程
1. **架构识别**:确定项目架构类型、识别关键组件和分层结构
2. **代码分析**:识别核心业务包和类、分析领域模型、提取接口定义、理解调用链路
3. **内容整理**:按文档结构组织信息、提取代码示例、绘制图表
4. **审核完善**:验证文档与代码一致性、补充关键信息、完善图表和示例
- **接口覆盖性验证**:确认总览文档中的所有接口都在详细文档中有完整描述
- **文档完整性检查**:确保没有遗漏任何必要的接口和服务描述
5. **定期更新**:与代码审查流程集成、重大变更更新文档、每季度全面审核
## 示例
### 领域模型示例
```markdown
## 核心实体关系图
```mermaid
classDiagram
classItem {
+Long id
+String name
+BigDecimal price
+String status
+validatePrice()
+changeStatus(String)
}
classTyingRule {
+Long id
+Long mainItemId
+List<Long> subItemIds
+Date startTime
+Date endTime
+enable()
+disable()
}
Item "1" -- "n" TyingRule: 被定义为主商品
TyingRule "1" -- "n" Item: 关联搭售商品
## 实体属性详细说明
### Item 商品实体
| 属性名 | 类型 | 说明 |
|----|---|---|
| id | Long | 商品唯一标识 |
| name | String | 商品名称,长度限制:2-50个字符 |
| price | BigDecimal | 商品价格,精确到小数点后2位,最小值:0.01 |
| status | String | 商品状态,枚举值:ON_SHELF(上架)、OFF_SHELF(下架)、DELETED(删除) |
#### 业务规则
- 商品价格必须大于0
- 商品状态只能按特定流程转换(上架->下架->删除)
### 业务流程示例
```markdown
## 搭售规则创建流程
### 核心流程图
```mermaid
flowchart TD
A[创建请求] --> B{校验参数}
B -->|无效| C[返回错误]
B -->|有效| D[查询主商品]
D --> E{商品存在?}
E -->|否| F[返回错误]
E -->|是| G[查询搭售商品]
G --> H{商品存在?}
H -->|否| I[返回错误]
H -->|是| J[保存规则]
J --> K[返回成功]
### 调用链路
**入口点**: `ItemTyingController.createTyingRule()`
**调用流程**:
1. 请求参数校验 - `validateTyingRequest(request)`
2. 查询主商品信息 - `itemService.getItemById()`
3. 校验主商品状态 - `validateItemStatus(item)`
4. 查询并校验搭售商品列表 - `validateSubItems()`
5. 构建并保存搭售规则 - `tyingRuleRepository.save()`
6. 发送规则创建事件 - `eventPublisher.publishEvent()`
### 关键判断点
| 判断点 | 条件 | 处理路径 |
|-----|---|----|
| 参数校验 | 主商品ID为空 | 返回参数错误 |
| 主商品校验 | 主商品不存在 | 返回商品不存在错误 |
| 搭售商品校验 | 存在无效商品 | 返回商品无效错误 |
# 代码分析规则
## 目标
根据代码入口深入分析完整业务流程,生成详细的业务流程文档,便于团队理解和维护代码。
## 关键规则
- **必须生成分析文档保存到项目的docs目录下**
- **必须使用sequential-thinking辅助分析**
- **必须深入方法内部逻辑,因此你可能需要检索代码**
- **建议使用sequential-thinking辅助检索代码**
### 1. 聚焦业务核心逻辑
- 忽略日志打印、参数基础校验等次要逻辑
- 忽略异常处理中的技术细节,只关注业务异常处理逻辑
- 忽略与业务无关的工具方法调用(如字符串处理、集合操作等)
- 聚焦业务状态转换、流程分支、核心计算等关键逻辑
### 2. 深入方法调用链
- 追踪每个关键方法的内部实现,不仅停留在方法调用层面
- 对调用链上的每个重要方法都分析其内部业务逻辑
- 对于外部依赖的服务(如HSF、RPC调用),说明其功能和业务意义
- 深入分析每个关键业务分支的条件和处理逻辑
### 3. 结合已有文档
- 优先使用已有文档中的描述,避免重复分析
- 如果已有文档对某个方法有详细描述,直接引用该内容
- "站在巨人的肩膀上",基于已有文档进行补充和完善
- 对已有文档与代码实现不一致的地方进行标注
### 4. 文档输出规范
- 分析结果保存到 `/docs` 目录下,使用 Markdown 格式
- 文档命名格式:`业务名称-流程分析.md`(如:`订单创建-流程分析.md`)
- 文档需包含方法调用树,清晰展示调用层级关系
- 使用分步业务流程描述完整处理过程
## 文档结构模板
```markdown
# 业务名称流程分析
## 功能概述
[简要描述该业务功能的目的和作用]
## 入口方法
`com.example.Class.method`
## 方法调用树
入口方法
├─ 一级调用方法1
│ ├─ 二级调用方法1.1
│ ├─ 二级调用方法1.2
├─ 一级调用方法2
├─ 二级调用方法2.1
└─ 二级调用方法2.2
└─ 三级调用方法
## 详细业务流程
1. [步骤1:业务逻辑描述]
2. [步骤2:业务逻辑描述]
- [子步骤2.1:详细逻辑]
- [子步骤2.2:详细逻辑]
3. [步骤3:业务逻辑描述]
## 关键业务规则
- [规则1:描述业务规则及其条件]
- [规则2:描述业务规则及其条件]
## 数据流转
- 输入:[描述方法输入及其业务含义]
- 处理:[描述关键数据处理和转换]
- 输出:[描述方法输出及其业务含义]
## 扩展点/分支逻辑
- [分支1:触发条件及处理逻辑]
- [分支2:触发条件及处理逻辑]
## 外部依赖
- 标注对外部系统的依赖
## 注意事项
- [列出实现中需要特别注意的点]
## 系统交互图
- 如果业务流程包含多个系统模块,请使用PlantUML画出时序图
## 代码分析技巧
### 步骤1:明确业务入口
- 确定代码分析的起点(通常是Controller、Facade或Service层的公开方法)
- 了解该方法的调用场景和业务背景
### 步骤2:构建方法调用树
- 从入口方法开始,追踪所有重要的方法调用
- 使用缩进表示调用层级,清晰展示调用关系
- 忽略非核心方法调用(如日志、参数校验等)
### 步骤3:分析业务流程
- 按照代码执行顺序分析业务处理步骤
- 重点关注业务状态转换和分支逻辑
- 提取关键业务规则和数据处理逻辑
### 步骤4:整理业务规则
- 总结条件判断中隐含的业务规则
- 分析不同场景下的处理差异
- 提炼业务逻辑的核心决策点
### 步骤5:描述数据流转
- 分析关键数据的来源、处理和去向
- 说明数据模型转换和业务含义
- 关注核心业务实体的状态变化
## 示例分析
参考 [订单查询.md](/docs/订单查询.md) 文档了解完整的分析示例:
该示例展示了订单查询业务的完整分析,包括:
- 方法调用树展示了完整调用链
- 详细业务流程按步骤拆解
- 关键业务规则清晰列出
- HSF接口等外部依赖明确说明
- 特殊处理逻辑如推广通退款按钮透出详细解释
## 好的分析的特征
1. **完整性**:覆盖所有核心业务逻辑和分支
2. **层次性**:清晰展示处理流程的层次结构
3. **业务性**:以业务视角描述,而非技术实现细节
4. **精确性**:准确反映代码的实际处理逻辑
5. **可理解性**:业务人员也能理解的表述方式
6. **实用性**:帮助读者快速理解业务流程和规则
- TRAE: Rules, Agent, Content, Pic…靠创新
参考(字节的技术文档还是太棒了)
https://mp.weixin.qq.com/s/v4ydYktNhFGp7AtRdm4Y7w
激活方式用户输入以下6A开头的内容即可启动工作流:激活时立即响应:6A工作流已激活
身份定义你是一位资深的软件架构师和工程师,具备丰富的项目经验和系统思维能力。你的核心优势在于:
上下文工程专家:构建完整的任务上下文,而非简单的提示响应规范驱动思维:将模糊需求转化为精确、可执行的规范质量优先理念:每个阶段都确保高质量输出项目对齐能力:深度理解现有项目架构和约束
6A工作流执行规则阶段1: Align (对齐阶段)目标: 模糊需求 → 精确规范
执行步骤
1. 项目上下文分析分析现有项目结构、技术栈、架构模式、依赖关系分析现有代码模式、现有文档和约定理解业务域和数据模型
2. 需求理解确认创建 docs/任务名/ALIGNMENT_[任务名].md包含项目和任务特性规范包含原始需求、边界确认(明确任务范围)、需求理解(对现有项目的理解)、疑问澄清(存在歧义的地方)
3. 智能决策策略自动识别歧义和不确定性生成结构化问题清单(按优先级排序)优先基于现有项目内容和查找类似工程和行业知识进行决策和在文档中回答有人员倾向或不确定的问题主动中断并询问关键决策点基于回答更新理解和规范
4. 中断并询问关键决策点主动中断询问,迭代执行智能决策策略
5. 最终共识生成 docs/任务名/CONSENSUS_[任务名].md 包含:明确的需求描述和验收标准技术实现方案和技术约束和集成方案任务边界限制和验收标准确认所有不确定性已解决
质量门控需求边界清晰无歧义技术方案与现有架构对齐验收标准具体可测试所有关键假设已确认项目特性规范已对齐
阶段2: Architect (架构阶段)目标: 共识文档 → 系统架构 → 模块设计 → 接口规范
执行步骤
1. 系统分层设计基于CONSENSUS、ALIGNMENT文档设计架构生成 docs/任务名/DESIGN_[任务名].md 包含:整体架构图(mermaid绘制)分层设计和核心组件模块依赖关系图接口契约定义数据流向图异常处理策略
2. 设计原则严格按照任务范围,避免过度设计确保与现有系统架构一致复用现有组件和模式
质量门控架构图清晰准确接口定义完整与现有系统无冲突设计可行性验证
阶段3: Atomize (原子化阶段)目标: 架构设计 → 拆分任务 → 明确接口 → 依赖关系
执行步骤
1. 子任务拆分基于DESIGN文档生成 docs/任务名/TASK_[任务名].md每个原子任务包含:输入契约(前置依赖、输入数据、环境依赖)输出契约(输出数据、交付物、验收标准)实现约束(技术栈、接口规范、质量要求)依赖关系(后置任务、并行任务)
2. 拆分原则复杂度可控,便于AI高成功率交付按功能模块分解,确保任务原子性和独立性有明确的验收标准,尽量可以独立编译和测试依赖关系清晰
3. 生成任务依赖图(使用mermaid)
质量门控任务覆盖完整需求依赖关系无循环每个任务都可独立验证复杂度评估合理
阶段4: Approve (审批阶段)目标: 原子任务 → 人工审查 → 迭代修改 → 按文档执行
执行步骤
1. 执行检查清单 完整性:任务计划覆盖所有需求 一致性:与前期文档保持一致 可行性:技术方案确实可行 可控性:风险在可接受范围,复杂度是否可控 可测性:验收标准明确可执行
2. 最终确认清单明确的实现需求(无歧义)明确的子任务定义明确的边界和限制明确的验收标准代码、测试、文档质量标准
阶段5: Automate (自动化执行)目标: 按节点执行 → 编写测试 → 实现代码 → 文档同步
执行步骤
1. 逐步实施子任务创建 docs/任务名/ACCEPTANCE_[任务名].md 记录完成情况
2. 代码质量要求严格遵循项目现有代码规范保持与现有代码风格一致使用项目现有的工具和库复用项目现有组件代码尽量精简易读API KEY放到.env文件中并且不要提交git
3. 异常处理遇到不确定问题立刻中断执行在TASK文档中记录问题详细信息和位置寻求人工澄清后继续
4. 逐步实施流程 按任务依赖顺序执行,对每个子任务执行:执行前检查(验证输入契约、环境准备、依赖满足)实现核心逻辑(按设计文档编写代码)编写单元测试(边界条件、异常情况)运行验证测试更新相关文档每完成一个任务立即验证
阶段6: Assess (评估阶段)目标: 执行结果 → 质量评估 → 文档更新 → 交付确认
执行步骤
1. 验证执行结果更新 docs/任务名/ACCEPTANCE_[任务名].md整体验收检查:所有需求已实现验收标准全部满足项目编译通过所有测试通过功能完整性验证实现与设计文档一致
2. 质量评估指标代码质量(规范、可读性、复杂度)测试质量(覆盖率、用例有效性)文档质量(完整性、准确性、一致性)现有系统集成良好未引入技术债务
3. 最终交付物生成 docs/任务名/FINAL_[任务名].md(项目总结报告)生成 docs/任务名/TODO_[任务名].md(精简明确哪些待办的事宜和哪些缺少的配置等,我方便直接寻找支持)
4. TODO询问 询问用户TODO的解决方式,精简明确哪些待办的事宜和哪些缺少的配置等,同时提供有用的操作指引
技术执行规范安全规范API密钥等敏感信息使用.env文件管理
文档同步代码变更同时更新相关文档
测试策略测试优先:先写测试,后写实现边界覆盖:覆盖正常流程、边界条件、异常情况
交互体验优化进度反馈显示当前执行阶段提供详细的执行步骤标示完成情况突出需要关注的问题
异常处理机制中断条件遇到无法自主决策的问题觉得需要询问用户的问题技术实现出现阻塞文档不一致需要确认修正
恢复策略保存当前执行状态记录问题详细信息询问并等待人工干预从中断点任务继续执行
- AI应用新范式:Embedding, Copilot, Agents(应用形态与我简历里的AI驱动软件开发新范式可对应)
4.架构与业务(架构与垂直领域)
4.1 BPM
4.1.1. BPMN概念
- Business Process Model and Notation,业务流程模型与标记法
BPMN 2.0 最革命性的变化在于引入了可执行性 (Executability)。这彻底改变了BPMN的定位。不仅要统一图形,更要统一其执行语义和文件格式。它的核心是“建模与执行”(Modeling and Execution)
- BPMN 2.0规范中有一百多个符号,但日常工作中80%的场景只需要用到其中不到20%的核心符号。
1.流对象 (Flow Objects):流程图中的主要图形元素。
-
事件 (Events):表示流程中发生的事情。用 圆圈 表示。
- 开始事件 (Start Event):一个细线圆圈,表示流程的起点。
- 结束事件 (End Event):一个粗线圆圈,表示流程的终点。
- 中间事件 (Intermediate Event):一个双线圆圈,表示流程进行中发生的事情(如等待、收到消息)。
- 消息时间 (Message Event):一个带箭头的圆圈,表示流程中接收或发送消息。
-
活动 (Activities):表示需要完成的工作。用 圆角矩形 表示。
- 任务 (Task):一个原子性的工作单元,不可再分。
(用户任务:当流程引擎走到一个用户任务时,它会暂停该流程实例的执行,并在其内部数据库中创建一个“待办任务”记录,并将其分配给BPMN图中指定的负责人(assignee)或候选组(candidate group),引擎会一直等待,直到有人通过外部应用(如您的Express应用)来“完成”这个任务;服务任务:需要由系统自动完成的工作(例如调用外部API、发送邮件、处理数据),对于Java来说,可以写一个Java的业务实现类,然后配置给流程引擎,由流程引擎调用。) - 子流程 (Sub-Process):一组相关的任务,可以折叠和展开。
- 任务 (Task):一个原子性的工作单元,不可再分。
-
网关 (Gateways):表示流程的分叉和合并。用 菱形 表示。
- 排他网关 (Exclusive Gateway):菱形里一个"X"。表示只有一个分支会被选择(类似if-else)。
- 并行网关 (Parallel Gateway):菱形里一个"+"。表示所有分支都将同时执行。
- 包容网关 (Inclusive Gateway):菱形里一个"O"。表示可以有一个或多个分支被选择。
2.连接对象 (Connecting Objects):用于连接流对象。
- 顺序流 (Sequence Flow):实线箭头,表示执行的顺序。
- 消息流 (Message Flow):虚线箭头,表示不同流程参与者之间的消息传递。
3.泳道 (Swimlanes):用于组织和分类活动。
- 池 (Pool):代表一个独立的流程参与者(如一个公司、一个部门)。
- 道 (Lane):在池内部,用于区分不同的角色或系统(如“申请人”、“经理”、“财务系统”)。
4.工件 (Artifacts):提供额外的信息。
- 数据对象 (Data Object):表示流程中需要用到的数据(如“报销单”)。
- 注释 (Annotation):对图表进行文字说明。
(
更复杂场景也支持,如:
不同类型的事件:定时器事件(等待特定时间)、消息事件(接收/发送消息)、错误事件等。
不同类型的任务:用户任务、脚本任务、服务任务等。
事务性子流程 (Transactions) 和 补偿 (Compensation)。
)
4.1.2. BPMN类系统/低代码系统 前端框架 Logicflow
LogicFlow 是一款滴滴客服技术团队开源的流程图编辑框架,它提供了一系列流程图交互、编辑所必需的功能和灵活的节点自定义、插件等拓展机制。
Apache-2.0开源、支持前端三大框架
(Logicflow用法上,和g2plot/echarts之类的用法类似,代码里各种配置参数)
(发散聊一下,前端框架很多,如UI框架Element UI/Plus、Ant等等,还有低代码前端的框架,还有基于三大框架开发的工程化的项目框架即web那一套动态路由等等工程上的问题也全封装好了)
4.1.3. BPMN类系统后端引擎(BPMN引擎)
Flowable / Camunda 8(Cloud Native):两大开源流行的BPMN 2.0流程引擎。可以直接在挡墙微服务里嵌入集成(即引包并配置),也可以独立部署(即单独部署一个服务,然后调用)。
问:floable和Camunda 提供拖拉拽设计流程页面吗?他们相当于是对xml进行编解码?举个例子:我在bpm建模器(如floable和Camunda)拖拉拽建模,会生成xml,然后java代码里集成floable和Camunda可以解析xml流程,然后执行。所以相当于代码永远不变,变的是xml?
答:
- Flowable和Camunda提供这个页面吗?
是的,它们都提供! 这就是我们之前提到的“BPMN建模器”,它是整个生态系统不可或缺的一部分。
Camunda: 提供一个名为 Camunda Modeler 的免费桌面应用程序。这是业界的黄金标准,非常强大和易用。
Flowable: 提供一个名为 Flowable Designer 的Web应用。它通常集成在Flowable的完整产品套件中,让你可以直接在浏览器里建模。
- 它们相当于是对XML进行编解码?
编码 (Encoding): 当您在BPMN建模器里拖拽图形、填写属性时,建模器正在实时地将您的可视化操作编码成符合BPMN 2.0标准的XML文件。您看到的图形是给人看的,背后的XML是给机器读的。
解码 (Decoding / Parsing): 当您的Java应用(集成了Flowable/Camunda)启动或部署这个XML文件时,流程引擎会读取这个文件,并将其解码成一个它可以在内存中理解和执行的、由Java对象构成的流程模型。
- 总结:
建模阶段: 您(或业务分析师)在BPMN建模器中通过拖拉拽的方式,设计或修改业务流程。完成后,您得到一个 .bpmn (本质是XML) 文件。
部署阶段: 您将这个 .bpmn 文件部署到流程引擎。对于嵌入式集成,通常就是把它放在Java项目的资源文件夹里,应用启动时引擎会自动加载并解析XML。
执行阶段: 您的Java代码(比如一个Controller)调用 runtimeService.startProcessInstanceByKey(…)。引擎接收到指令,根据它已经解析好的流程模型,开始一步步执行流程。
监控阶段:自定义
- 所以相当于代码永远不变,变的是XML
我们把您的Java代码分成两类来看,这个概念会更清晰:
a) “流程控制”代码 (The Orchestration Code) - 这部分【永远不变】
这部分代码就是您在Express或Spring里写的那些通用的、与具体业务步骤无关的代码。
startProcess(…)
getTasksForAssignee(…)
completeTask(…)
这些代码就像一个CD播放机。它非常通用,它的功能就是“播放光盘”、“查询曲目”、“下一首”。不需要为了换一张CD而改造播放机本身。
b) “具体工作”代码 (The Implementation Code) - 这部分【可能需要补充】
这部分代码是指BPMN流程图中“服务任务(Service Task)”背后需要执行的具体业务逻辑,通常是一个JavaDelegate类。
这就像是CD播放机的特效插件。
如果流程变化只是调整顺序、修改条件、增减人工审批步骤:
比如审批金额从5000改成3000。比如在总监审批后增加一个“副总审批”的人工任务。
在这种情况下,您只需要修改XML(换一张CD),Java代码一行都不用动。
如果流程变化是增加了一个【全新的自动化步骤】:
比如业务部门要求:“在财务审批通过后,系统需要自动调用ERP系统接口来创建凭证。”
这时,需要在BPMN图上增加一个“服务任务”,并告诉它需要调用一个名为 ErpIntegrationDelegate 的Java类。如果代码库里还没有这个 ErpIntegrationDelegate.java 文件,那么就需要新增这个Java类(安装一个新的“特效插件”)。
但是,原有的“流程控制”代码(播放机本身)依然不需要改变。
最终结论
90%的情况下,业务流程的变更都只是逻辑、顺序和条件的调整。对于这些变更,您只需要修改BPMN图(即XML),而您的核心代码库保持稳定、无需改动。
这实现了业务流程的热插拔,将易变的业务规则从相对稳定的程序代码中解耦出来,这正是流程引擎带来的革命性优势。
以单独部署流程引擎服务为例:
1.启动流程:
前端: 用户填写申请表单。
Java后端: 接收表单数据,调用 ApprovalService.startProcess(processDefinitionKey, variables)会返回id。
流程引擎: 启动流程实例,创建第一个用户任务(“经理审批”)。
(processDefinitionKey会指向.bpmn文件即流程定义,.bpmn 文件本质上是一个遵循 BPMN 2.0 XML Schema 定义的 XML 文档)
2.经理审批 (用户任务):
前端: 经理登录,调用 GET /api/process/tasks/managerId
Java后端: 查询任务,返回给前端(==和流程引擎交互获取流程信息==)。
前端: 展示“经理审批”任务。经理点击“批准”。
前端: 调用 POST /api/process/tasks/{taskId}/complete,传入 approved: true。
Java后端: 调用 ApprovalService.completeTask()。
流程引擎: 收到完成指令,根据BPMN图和approved变量,流程流转到“总监审批”用户任务。
3.总监审批 (用户任务):
重复经理审批的步骤,只是assignee变成了总监。
4.财务审批 (用户任务):
重复总监审批的步骤,只是assignee变成了财务。
5.发送通知邮件 (服务任务):
如果BPMN图中有这个服务任务,当流程流转到它时,流程引擎会自动调用您在Java后端实现的 SendEmailDelegate 的 execute 方法。
SendEmailDelegate 执行邮件发送逻辑。
流程引擎继续推进流程,直到结束。
流程定义(BPMN XML)和流程实例状态(运行时数据)之间的根本区别:
建筑蓝图 vs. 正在建造的房子
BPMN XML文件(.bpmn):
流程定义。它是静态的,可移植的,任何符合BPMN 2.0标准的引擎都能解析。
这就像一张建筑蓝图。它详细定义了房子的结构、房间布局、水管电线的走向、门窗的位置、以及各种施工规范。
它是一个静态的、通用的设计图纸。
它不包含任何关于“正在建造的房子”的信息:比如房子盖到第几层了,哪个工人正在哪个房间里施工,墙刷了什么颜色,里面住了谁。
流程引擎的数据库(运行时数据)(流程引擎支持N种数据库,==会自动在连接的数据库里面建表,自动管理Schema==):
是流程执行器和状态管理者。每个引擎实例都有自己的数据库来存储运行时(Runtime)的流程实例状态。
这就像正在建造的房子本身。它记录了:
这栋房子(某个流程实例)的唯一ID。
房子目前盖到了哪一步(流程执行到哪个节点)。
房子里有哪些家具(流程变量,比如申请金额、审批意见)。
哪个工人(用户)正在哪个房间(任务)里工作。
房子的历史(之前盖了哪些部分,谁盖的)。
这些信息是动态的、特定于某个实例的。每个流程引擎都有自己内部管理运行时数据的数据库表结构和状态管理逻辑。这些表结构虽然概念上相似(都有任务表、流程实例表),但具体字段、索引、内部ID生成方式等可能存在差异。
更多推荐


所有评论(0)