小米大模型一面
自我介绍¶
GPT、LLaMA、Qwen 注意力机制的区别¶
这三大家族模型都基于 Transformer 解码器架构,但在注意力机制的具体实现和优化上存在显著差异,主要体现在位置编码、注意力变体和长文本扩展策略上。
GPT 系列:
-
位置编码:早期的 GPT-1/GPT-2 使用可学习的位置嵌入(Learned Positional Embedding),每个位置有一个独立的嵌入向量,与词嵌入相加。缺点是位置编码的长度在训练时固定,无法直接外推到更长的序列。
-
注意力机制:标准的多头自注意力(MHA),每个头有独立的 Q、K、V 投影。在自回归生成时使用因果掩码,确保当前位置只能看到过去的信息。
-
长文本扩展:GPT-3 及以后的部分版本仍使用可学习位置嵌入。OpenAI 在 GPT-3.5/GPT-4 的细节未完全公开,但已知 GPT-4 支持超长上下文(128K),可能采用了类似于 RoPE 或改进的位置编码以及稀疏注意力等技术,但未明确说明。
-
优化:FlashAttention 等加速实现被广泛使用。GPT 系列的注意力结构相对传统,更侧重于通过数据和规模取胜,而非精巧的注意力变体。
LLaMA 系列:
-
位置编码:RoPE(旋转位置编码)。通过旋转矩阵将相对位置信息注入 Q 和 K 的点积中,使模型能够自然地捕捉相对位置关系。RoPE 具有良好的理论性质和外推潜力。
-
注意力机制:LLaMA 1 使用标准多头注意力。从 LLaMA 2 开始,引入了 GQA(分组查询注意力)。在较大的模型中(如 70B),Q 的头数保持不变,但 K 和 V 的头数减少(分组共享),在速度和效果之间取得平衡,大幅降低推理时的 KV 缓存。
-
长文本扩展:基于 RoPE,LLaMA 社区通过位置插值(PI)、NTK-Aware 缩放、YaRN 等方法,在不重新训练或少量微调的情况下将上下文窗口扩展到 32K、128K 甚至更长。LLaMA 3 原生支持 8K 上下文。
-
其他优化:LLaMA 使用 Pre-Norm + RMSNorm 结构,激活函数为 SwiGLU,注意力部分并未做过多复杂变体,保持简洁高效。
Qwen 系列:
-
位置编码:同样采用 RoPE,与 LLaMA 一致,具备良好的外推能力。
-
注意力机制:Qwen 模型中采用了多头注意力(MHA)或分组查询注意力(GQA)的混合策略。Qwen-7B 使用标准 MHA,更大的模型可能采用 GQA 来降低推理成本。此外,Qwen 在注意力计算中引入了LogN 缩放(LogN-Scaling),对注意力 logits 进行基于层索引的缩放,以稳定深层网络的训练。
-
长文本扩展:Qwen 支持动态 NTK 插值和窗口注意力的组合。它可以根据输入序列的长度动态调整 RoPE 的频率,使模型能够平滑地处理比训练时更长的上下文。同时,Qwen 提出了 Dual Chunk Attention 等机制来高效处理超长序列,兼顾了全局和局部注意力。
-
独特设计:Qwen 在多模态版本(Qwen-VL)中,引入了视觉语言适配器,其注意力机制需要处理额外的视觉 token,因此在注意力掩码和位置编码分配上有专门设计。Qwen 还使用了FlashAttention v2 进行加速。
核心区别总结表:
| 维度 | GPT(早期) | LLaMA | Qwen |
|---|---|---|---|
| 位置编码 | 可学习位置嵌入 | RoPE | RoPE |
| 注意力变体 | MHA | MHA / GQA | MHA / GQA |
| 长文本方案 | 固定窗口/未公开 | PI, NTK, YaRN 插值 | 动态NTK, 窗口注意力, LogN缩放 |
| 推理加速 | FlashAttention | FlashAttention, GQA | FlashAttention v2, GQA |
| 特色 | 传统结构,重规模 | 简洁高效,社区生态丰富 | 训练稳定,原生长文本支持好 |
Agent 项目的数据形式和 Agent-as-Tool 实现细节¶
数据形式:
我们的 Agent 项目涉及多种数据类型,在训练和推理中以统一格式流转:
-
文本:用户输入、模型思考、工具调用指令和结果、最终回答。所有文本采用 ChatML 格式(
<|im_start|>role\ncontent<|im_end|>)组织多轮对话。 -
工具调用:采用 JSON 格式嵌入在 assistant 消息中,例如:
-
工具返回结果以
<|im_start|>tool\n{...}<|im_end|>格式追加。 -
多模态数据:图片以 base64 编码或本地路径形式嵌入
<img>path</img>标签,在送入模型前由视觉编码器处理为视觉 token,并插入到文本 token 序列中。 -
训练数据:由多轮 agent 轨迹构成。每一条轨迹包含完整的 ReAct 循环:用户指令 → 模型思考 → 工具调用 → 工具返回 → ... → 最终答案。这些轨迹被切分为多个训练样本,loss 只在模型输出的部分计算(assistant 的思考、动作、最终答案),系统提示、用户消息和工具返回不计算 loss。
-
评估数据:包含输入、期望的工具调用序列、期望的最终答案,以及人工评判的质量标签。用于离线评测和奖励建模。
Agent-as-Tool 如何实现:
“Agent-as-Tool” 是指将一个子 Agent 封装成一个可被父 Agent 调用的工具。这实现了层次化任务分解。具体实现:
-
接口标准化:每个子 Agent 被包装成一个符合 MCP(或自定义)标准的工具。它具有标准的 JSON Schema 描述其功能、输入参数(如任务描述、上下文)和输出格式(如任务结果、状态)。
-
独立性:子 Agent 拥有自己独立的上下文、记忆空间和工具集。它可以是专门解决某类问题的专家(如代码解释 Agent、数据分析 Agent)。
-
调用流程:
- 父 Agent 规划任务时,决定将某个子任务委派给子 Agent。
- 父 Agent 生成一个 Function Call,工具名即为子 Agent 的名字,参数为对子 Agent 的任务描述和必要上下文。
- 调度器接收到该调用后,启动一个新的 Agent 会话(或复用已有会话),将参数作为初始指令传递给子 Agent。
- 子 Agent 独立完成其 ReAct 循环,最终返回结果(文本、文件、结构化数据)。
-
父 Agent 收到子 Agent 的结果作为 Observation,继续其推理。
-
权限与沙箱:子 Agent 通常运行在受限的环境中,拥有比父 Agent 更少的权限。父 Agent 只允许调用指定的子 Agent,且子 Agent 不能反向调用父 Agent,以防止死循环和权限逃逸。
-
状态传递:父 Agent 可以传递部分上下文给子 Agent(如用户原始问题),但子 Agent 无法访问父 Agent 的完整记忆,保证隔离性。
-
实现方式:在 LangGraph 中,父 Agent 的图上有一个节点专门处理“调用子 Agent”的动作。该节点创建一个新的线程/子图来运行子 Agent,并将最终结果作为该节点的输出返回。
通过 Agent-as-Tool,我们可以构建分层的多智能体系统,将复杂任务逐层分解,每个层级专注于自己的职责,极大提升了系统的可扩展性和能力上限。
微调项目:SFT 数据组织、训练轮次、超参设置及最优参数搜索¶
SFT 数据组织形式:
-
采用 ChatML 格式 统一表示多轮对话和工具调用。
-
每条样本是一个完整的对话历史,包含
system、user、assistant角色。对于多轮工具调用,还包括tool角色。 -
系统提示(system prompt)在每条样本的开头,定义模型的角色、行为规范和输出格式。
-
对于多轮对话,通过滑动窗口切分。确保每条样本的上下文长度不超过模型的最大长度(如 2048 token),且不会截断在某轮对话的中间。同一个会话的所有片段划分为训练集或验证集,不会跨集合泄漏。
-
数据格式示例:
<|im_start|>system
You are a helpful assistant.<|im_end|>
<|im_start|>user
请帮我查询天气<|im_end|>
<|im_start|>assistant
好的,我需要调用天气工具。{"action": "get_weather", ...}<|im_end|>
<|im_start|>tool
{"temperature": 25, "condition": "晴"}<|im_end|>
<|im_start|>assistant
当前天气晴朗,气温25℃。<|im_end|>
- Loss 只计算
assistant角色输出的部分,system、user、tool的内容不参与 loss 计算。
训练轮次(Epochs)选择:
-
对于 SFT,通常训练 2~4 个 epoch。过少则欠拟合,过多则过拟合且灾难性遗忘加重。
-
根据数据量:若数据量较小(数千条),可训练 4-5 epoch;数据量大(数万条以上),2-3 epoch 即可。
-
通过监控验证集上的 loss 和下游任务指标,当验证 loss 不再下降或开始上升时提前停止(early stopping)。
超参设置及理由:
-
学习率:5e-5 ~ 2e-4(全参微调时)或 1e-4 ~ 5e-4(LoRA 微调时)。LoRA 可训练参数少,需要稍大的学习率以有效更新。
-
学习率调度:使用余弦退火(Cosine Annealing),搭配 warmup(占总步数的 10%)。Warmup 使训练初期稳定,余弦退火有助于收敛到更优的平坦区域。
-
批次大小:全局批次大小 32~128。通过梯度累积实现,micro batch size 受限于显存(如 1~4)。更大的批次有助于训练稳定,但过大会降低泛化性。
-
优化器:AdamW,权重衰减 0.01~0.1,betas=(0.9, 0.999)。AdamW 能有效解耦权重衰减和自适应学习率,提升泛化。
-
最大序列长度:2048 或 4096。根据任务和显存选择,覆盖大部分训练样本而不浪费计算。
-
Dropout:通常设为 0 或极小值(如 0.05),因为大模型本身不易过拟合,且 LoRA 参数少,dropout 可能阻碍学习。
为什么这么设置:
这些设置借鉴了 LLaMA、Alpaca、Vicuna 等主流 SFT 工作的最佳实践,并在我们的数据和任务上通过小规模实验验证。LLM 微调属于迁移学习,基座模型已经很强,我们只需在适当的学习率下进行少量 epoch 的微调,配合 warmup 和余弦退火,即可快速适配下游任务而不致于遗忘通用能力。
如何寻找最优参数:
-
搜索空间定义:选择关键超参(学习率、epoch、batch size),对每个超参设定候选值列表。
-
小规模实验:在全部训练数据的一个子集(如 10%)上进行独立实验,使用网格搜索或贝叶斯优化(如 Optuna),以验证集上的 loss 和下游任务评分为目标。
-
逐步细化:先粗粒度搜索,确定大致范围;再在较优区域进行细粒度搜索。
-
交叉验证:对于小数据集,进行 k 折交叉验证,取平均最佳超参。
-
监控指标:除了 loss,还关注模型在几个代表性评测集上的表现(如 MMLU、TruthfulQA),以及生成质量的自动评分。最终选择在这些指标上帕累托最优的参数组合。
LoRA、QLoRA 原理、区别,模型量化方法及原因¶
LoRA(Low-Rank Adaptation)原理:

QLoRA(Quantized LoRA)原理:
QLoRA 在 LoRA 的基础上,将基座模型 W0W0 量化为 4-bit(NF4 数据类型),进一步降低显存占用。它引入了三项关键技术:
-
NF4 数据类型:一种适用于正态分布权重的 4-bit 量化格式,比均匀量化信息损失更小。
-
双重量化(Double Quantization):对量化所需的缩放因子也进行量化,节省更多内存。
-
分页优化器(Paged Optimizers):利用 NVIDIA 的统一内存分页机制,在 GPU 内存不足时自动将优化器状态交换到 CPU 内存,避免 OOM。 训练时,量化的基座模型权重被冻结,只在前向传播时反量化为 16-bit 精度,梯度和优化器状态仅应用于 LoRA 的低秩矩阵。
LoRA 与 QLoRA 的区别:
| 维度 | LoRA | QLoRA |
|---|---|---|
| 基座模型精度 | 保持原精度(如 FP16/BF16) | 量化为 4-bit NF4 |
| 显存占用 | 相对全参微调大幅降低,但模型仍需 16-bit 存储 | 进一步降低约 4 倍,可在单张消费级 GPU 上微调 65B 模型 |
| 训练速度 | 较快,无量化反量化开销 | 略慢,因需反量化计算 |
| 性能 | 接近全参微调 | 通过先进量化技术,性能与 LoRA 几乎持平 |
| 适用场景 | 资源较充足时 | 显存极度受限时 |
模型量化的方法和原因:
- 量化方法:将浮点权重和激活值映射到低比特整数(如 INT8、INT4)。常用方法有:
- 训练后量化(PTQ):直接对已训练模型进行量化,无需重新训练。代表算法:GPTQ(基于近似二阶信息)、AWQ(激活感知权重量化)、GGUF(用于 CPU 推理)。
-
量化感知训练(QAT):在训练中模拟量化误差,使模型适应量化后的分布,精度更高但成本大。
-
为什么量化:
- 降低内存占用:将模型从 16-bit 压缩到 4-bit,大小减少约 4 倍。
- 加速推理:低比特计算可利用硬件(如 Tensor Cores 的 INT8 加速)获得更高吞吐,并降低内存带宽压力。
- 降低能耗与成本:使大模型能够在边缘设备或低成本 GPU 上运行。
- QLoRA 中的量化进一步使得在极有限的硬件上微调千亿大模型成为可能。
SFT 数据整理成 ChatML 格式(手撕代码)¶
任务:将原始的 SFT 对话数据(通常为包含 role 和 content 的 JSON 列表)转换为 ChatML 格式的字符串。
代码实现(Python):
def convert_to_chatml(messages):
"""
messages: list of dict, each with 'role' and 'content'
returns: ChatML formatted string
"""
chatml_parts = []
for msg in messages:
role = msg['role']
content = msg['content']
# 拼接单个消息
chatml_parts.append(f"<|im_start|>{role}\n{content}<|im_end|>")
# 拼接所有消息,并在末尾添加 assistant 起始标记(如果最后一轮是 assistant 回答)
# 对于训练,通常最后是 assistant 的回答,结束时加上 <|im_end|>
return "\n".join(chatml_parts)
# 示例
messages = [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "你好,请帮我查天气。"},
{"role": "assistant", "content": "好的,正在查询..."},
{"role": "tool", "content": '{"temp": 25}'},
{"role": "assistant", "content": "当前气温25℃。"}
]
print(convert_to_chatml(messages))
输出:
<|im_start|>system
You are a helpful assistant.<|im_end|>
<|im_start|>user
你好,请帮我查天气。<|im_end|>
<|im_start|>assistant
好的,正在查询...<|im_end|>
<|im_start|>tool
{"temp": 25}<|im_end|>
<|im_start|>assistant
当前气温25℃。<|im_end|>
关键点:
-
使用特殊的起止标记
<|im_start|>和<|im_end|>,这是 ChatML 的核心。 -
每个角色占一行,内容紧随其后,末尾明确标记结束。
-
训练时只对
assistant角色的内容计算 loss,因此 tokenization 后需要生成相应的 mask。 -
确保
content不包含这些特殊标记,否则需转义。
场景题:SFT 数据准备好了,如何做一个意图识别?完整流程¶
意图识别任务定义:根据用户输入的自然语言,判断其属于哪个预定义类别(如“查询订单”、“退款申请”、“咨询产品”等),并提取相关槽位(如订单号、产品名)。
完整流程:
-
数据准备与标注
-
收集大量真实或模拟的用户查询,覆盖各种意图类别和表述变体。
-
为每条查询标注:
- 意图类别(多分类):
intent(如query_order,refund,product_inquiry)。 -
关键槽位(命名实体识别/信息抽取):如
order_id、product_name、date。 -
确保数据平衡,每个意图类别样本量足够。对于少样本类别,进行上采样或数据增强(回译、同义改写)。
-
数据格式转换(SFT 格式)
-
将标注数据整理为指令微调格式,用于训练大模型。格式示例:
{
"messages": [
{"role": "system", "content": "你是一个意图识别助手。根据用户输入,识别意图并抽取槽位。意图类别包括:query_order, refund, product_inquiry。"},
{"role": "user", "content": "我想查一下我的订单,单号是A12345"},
{"role": "assistant", "content": "{\"intent\": \"query_order\", \"slots\": {\"order_id\": \"A12345\"}}"}
]
}
-
要求输出为结构化 JSON,便于下游解析和评测。
-
模型选择与微调
-
基座模型:选择一个较小但强大的 LLM(如 Qwen-7B、LLaMA-3-8B),以平衡效果和推理延迟。
-
微调方法:使用 LoRA(或全参,若资源允许)进行监督微调。训练时只计算 assistant 回复的 loss。
-
超参设置:学习率 2e-4,batch size 64(通过梯度累积),2-3 epochs,余弦退火+warmup。
-
评测与迭代
-
构建独立的测试集,包含各类意图以及难以判断的边缘 case。
-
评测指标:
- 意图准确率:模型输出的 intent 是否与标注一致。
- 槽位 F1 分数:槽位抽取的精确率、召回率和 F1。
-
端到端准确率:意图和所有槽位完全正确的比例。
-
分析 bad case,针对性地增加训练数据(如易混淆意图的对比样本、少样本类别的增强)。
-
若模型在某些意图上偏弱,可尝试构造专门的对比微调数据或使用 DPO 偏好对齐。
-
部署与优化
-
将微调后的模型部署为在线服务,使用 vLLM 或 TGI 提高吞吐。
-
推理约束:采用 guided generation 确保输出必为合法 JSON,并使用预定义的 intent 值域进行约束解码,杜绝无效输出。
-
不确定性处理:若模型输出置信度低(可通过 logits 或多次采样评估),则启动多轮澄清或转接人工。
-
持续迭代:收集线上日志中的误判案例,定期重新标注并微调模型,形成闭环优化。
流程总结:数据标注 → SFT 格式构造 → 基座模型 LoRA 微调 → 多指标评测 → 基于 bad case 的数据增强与再训练 → 上线部署 + 推理约束 → 线上反馈持续迭代。通过这一套流程,可以快速构建一个高准确率、稳定输出的意图识别系统。