简介Happy-LLM从零开始的大语言模型原理与实践教程.pdf 面向希望系统理解大语言模型原理并动手实践的学习者尤其适合已接触过开源大模型使用指南、想进一步弄清训练过程与架构机制的读者。教程以 NLP 基础为起点逐步展开 Transformer 架构、自注意力机制、语言模型训练方法与 LLaMA2 完整实现并延伸到 RAG、Agent 等应用场景兼顾理论梳理与项目实操。压缩包内共 1 个 PDF 文件约 19.06MB内容按章节组织便于从基础概念一路读到模型构建与训练实践也适合作为课程配套或自学路线参考。目前已有 558 人学习。读者可借此建立从分词、模型结构到训练流程的整体认知理解现有大模型的工作机制并跟着教程完成一个小型 LLM 的搭建、训练与测试同时掌握 LoRA/QLoRA 微调、模型评估及 RAG、Agent 等落地思路形成可复用的实践路径。1. 从一份 PDF 到一次完整训练这份教程把 LLM 的哪几块拆开了很多人能把transformers的推理脚本跑通模型也能吐出像模像样的句子可一旦被追问「注意力里为什么要除以根号 d」「SFT 的 loss 为什么只算回答部分」就卡住了。Happy-LLM 这份《从零开始的大语言模型原理与实践教程》针对的正是这个断层它从 NLP 基础概念起步经 Transformer 手写实现、三类预训练语言模型架构对比一路推到 LLaMA2 的 Tokenizer 训练、预训练与 SFT 全流程最后落到 LoRA/QLoRA 微调、RAG 与 Agent 的最小实现。适合会 Python、跑过推理但没写过训练循环的开发者也适合要在大模型方向做课程设计或准备转岗面试的人。它不是速查手册而是要求你跟着敲代码、把每个张量形状对上的那种教程。2. Transformer 手写实现把注意力拆到矩阵维度上读开源模型代码时最常见的卡点是形状对不上明明知道多头注意力是把 Q、K、V 拆成若干头分别算但view之后该不该transpose、mask 该加在哪一步、softmax 沿哪个维度归一化这些细节在框架封装里全被藏起来了。这一章的做法是反着来——先用几十行 PyTorch 把缩放点积注意力和多头拆分写出来再去看 LLaMA2 的实现两边一一对应后面的训练代码才有落脚点。2.1 先写单头注意力四个矩阵乘法的顺序不能乱缩放点积注意力的公式很短但落到代码里顺序错一步结果就完全不对。我一般会先写单头版本把每一步的形状打印出来确认无误再封装成多头。import math import torch import torch.nn as nn import torch.nn.functional as F def scaled_dot_product_attention(q, k, v, maskNone): q: [B, H, T_q, D_h] k: [B, H, T_k, D_h] v: [B, H, T_k, D_h] mask: [B, 1, T_q, T_k] 或 [1, 1, T_q, T_k]True 表示可见 返回: [B, H, T_q, D_h] d_k q.size(-1) # 1. Q 乘 K 的转置得到 [B, H, T_q, T_k] 的相似度矩阵 scores torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(d_k) # 2. mask 必须在 softmax 之前加用极小值堵住未来位置 if mask is not None: scores scores.masked_fill(~mask, torch.finfo(scores.dtype).min) # 3. softmax 沿最后一个维度也就是每个 query 看所有 key的方向 attn F.softmax(scores, dim-1) # 4. 用权重加权求和 value return torch.matmul(attn, v), attn四个动作对应四行代码顺序不能调换。第一步除以sqrt(d_k)是为了防止点积随维度增大而方差爆炸导致 softmax 进入饱和区、梯度趋近于零这一步在早期实现里经常被漏掉表现是训练前几百步 loss 几乎不动。第二步的 mask 用masked_fill而不是乘 0是因为 softmax 里乘 0 之后 exp(0)1 仍然有贡献必须填负无穷。第三步dim-1是最容易写错的地方写成dim-2会把不同 query 之间的分数归一化模型完全学不到东西但代码不报错这是典型的静默失败。第四步返回注意力权重是为了后面画热力图、做可视化排查。参数上还有两个隐含约定。一是q和k的最后一维必须相等否则矩阵乘法直接报错这个错误信息很明确反而好查二是mask的 dtype 建议用 bool用 0/1 的 float 去乘会在混合精度下产生意外的 0 值。我在调试阶段会固定加一行形状断言跑通后再删掉比事后猜要省时间。2.2 多头拆分三次 view 和一次 transpose多头注意力的本质是把d_model切成n_heads份每份独立算注意力再拼回来。代码看起来只是几个 reshape但transpose的位置决定了内存布局写错了要么结果错、要么虽然结果对但慢得离谱。class MultiHeadAttention(nn.Module): def __init__(self, d_model512, n_heads8, dropout0.1): super().__init__() assert d_model % n_heads 0, d_model 必须能被 n_heads 整除 self.d_model d_model self.n_heads n_heads self.d_head d_model // n_heads # 每个头的维度 # 一次性投影出 QKV比三个独立 Linear 少两次 kernel 启动 self.qkv_proj nn.Linear(d_model, 3 * d_model) self.out_proj nn.Linear(d_model, d_model) self.dropout nn.Dropout(dropout) def forward(self, x, maskNone): B, T, _ x.shape # [B, T, 3*d_model] - [B, T, 3, H, D_h] qkv self.qkv_proj(x).view(B, T, 3, self.n_heads, self.d_head) # 拆成三份并把 head 维度提到 batch 后面 - [3, B, H, T, D_h] q, k, v qkv.permute(2, 0, 3, 1, 4).unbind(0) out, _ scaled_dot_product_attention(q, k, v, mask) # 拼回 [B, T, d_model] out out.transpose(1, 2).contiguous().view(B, T, self.d_model) return self.dropout(self.out_proj(out))view(B, T, 3, H, D_h)这一步把最后一维切成「三份投影 × 头数 × 头维度」顺序必须是 3 在前、H 在后因为我们希望 Q、K、V 各自连续存放。permute(2, 0, 3, 1, 4)之后unbind(0)得到三个[B, H, T, D_h]这个顺序才能让matmul把最后两维当成矩阵、前面两维当成批次。拼回来时的transpose(1, 2)如果漏了后面的.contiguous()view会因为内存不连续直接报错——这个报错信息很直白属于好处理的坑。真正需要留意的是d_model和n_heads的关系。d_model512, n_heads8时每个头 64 维这是经典配置LLaMA2 用的是d_model4096, n_heads32每头 128 维。头数不是越多越好头维度太小比如 16 维会让每个头学到的表征容量不足实践中很少低于 32。另一个细节是 GQA分组查询注意力LLaMA2 的 70B 版本里 K、V 的头数少于 Q这是为了推理时省显存手写实现阶段可以先按标准多头来等能跑通了再改。2.3 位置编码与残差loss 不降时先查这两处自注意力本身对位置是无感的——打乱输入顺序输出只是跟着重排模型不知道谁在前谁在后。所以位置编码必须显式注入而注入方式直接决定模型能不能训起来。教程里给了正弦位置编码的手写版本这个版本的好处是不用训练参数、可以外推到比训练长度更长的序列。import math def sinusoidal_position_encoding(max_len, d_model, devicecpu): 返回 [1, max_len, d_model] 的位置编码可直接加到词嵌入上 pe torch.zeros(max_len, d_model, devicedevice) position torch.arange(0, max_len, devicedevice).unsqueeze(1).float() # 不同维度用不同频率偶数维用 sin、奇数维用 cos div_term torch.exp( torch.arange(0, d_model, 2, devicedevice).float() * (-math.log(10000.0) / d_model) ) pe[:, 0::2] torch.sin(position * div_term) pe[:, 1::2] torch.cos(position * div_term) return pe.unsqueeze(0)div_term里那个10000.0是频率基数改大会让高频分量衰减更快、长距离位置区分度下降d_model必须能被 2 整除否则0::2和1::2的长度不一致会报错。这套编码在序列长度超过训练长度时仍然能给出一致的相对关系但现代 LLM 大多换成了 RoPE旋转位置编码原因在于 RoPE 把位置信息编码进 Q、K 的相对角度里配合外推策略在长上下文上更稳。手写这一版的价值不在于用而在于理解「位置信息必须显式构造」这件事。残差结构的问题更隐蔽。Post-LN先注意力后 LayerNorm在深层网络里需要精心设计的 warmup否则前几百步的梯度范数会剧烈震荡Pre-LN先 LayerNorm 再注意力把归一化放在子层入口梯度回传路径更短训练稳定得多这也是现在几乎所有 LLM 的选择。如果你手写的模型出现 loss 先降后爆、或者梯度范数在 10 的几次方之间跳八成是这里的问题。改法很简单把x layer_norm(x attn(x))换成x x attn(layer_norm(x))同时把最终的layer_norm加在输出头之前。这个改动只有两行但效果往往是「从训不动到能训」的差别。3. 架构选型Encoder-Only、Encoder-Decoder 与 Decoder-Only 的掩码差别教程用一整章讲三类预训练语言模型从 Encoder-Only 的 BERT 路线到 Encoder-Decoder 的 T5 路线再到 Decoder-Only 的 GPT/LLaMA 路线。这部分内容如果只当科普读读完就忘真正有价值的是把它们还原成「注意力掩码长什么样」这个具体问题——三类架构的差别落到代码上其实就是一个 mask 矩阵的形状。3.1 一张表看懂三种掩码掩码决定了「谁能看到谁」。Encoder-Only 用双向可见每个 token 都能看到整句Encoder-Decoder 的编码器双向、解码器单向加交叉注意力Decoder-Only 用下三角因果掩码每个位置只能看自己和左边。这个差别在训练目标和推理方式上都会放大。架构可见性训练目标典型用途掩码形状Encoder-Only全双向MLM随机遮盖预测分类、抽取、句向量全 TrueEncoder-Decoder编码器双向 解码器因果Span 去噪 / seq2seq翻译、摘要、结构化生成双 mask 交叉 maskDecoder-Only因果下三角下一 token 预测CLM对话、通用生成、Agent下三角 True表格里「掩码形状」一列是关键。Encoder-Only 的 mask 可以是None也可以用全 True 的方阵Decoder-Only 的 mask 必须是下三角矩阵用torch.tril(torch.ones(T, T))生成Encoder-Decoder 则要同时维护两份交叉注意力那里还要额外处理 padding。三类架构的训练目标不同导致同一个下游任务选错架构会很别扭拿 Decoder-Only 做文本分类得在最后位置接一个分类头效果通常不如直接用 Encoder-Only拿 Encoder-Only 做开放式生成模型根本没有下一 token 预测的训练经验生成质量会明显掉档。这里有个常见的实操结论当下的通用大模型几乎全是 Decoder-Only不是因为另外两类不行而是因为它的训练目标下一 token 预测对数据的要求最低——任何一段文本都能当训练样本不需要构造配对、也不需要人工标注遮盖位置。规模上去之后上下文学习能力就是从这个简单目标里涌现出来的。3.2 从 PLM 到 LLM规模、指令微调与三个阶段教程把 LLM 的训练过程拆成三段这个拆分方式在动手之前必须先建立起来否则你会在「我的小模型为什么不听话」这个问题上反复浪费时间。第一段是预训练目标就是下一 token 预测语料是海量无标注文本产出的是一个会续写但不会听话的基座模型。这个阶段的计算量最大个人开发者基本不可能从头训一个有实用价值的模型教程里的小模型定位是跑通流程、理解数据流。第二段是有监督微调SFT数据是「指令-回答」对loss 只算回答部分。这一步把基座模型从「续写器」掰成「助手」样本量通常只有几万到几十万条但对格式极其敏感。第三段是对齐RLHF/DPO 一类需要偏好数据。教程在实战章里以 SFT 和 LoRA 为主对齐部分点到为止这符合个人开发者的现实前两段能跑通就已经能覆盖大部分工程需求。这个三段式还有个容易忽略的推论如果你手里的基座模型已经在海量语料上预训练过那你只需要做后面两段这也正是 LoRA 微调能奏效的前提。反过来如果你从随机初始化开始训一个几千万参数的小模型那它只能学到格式学不到知识这个预期要先摆正。3.3 选型判断与三个高频误用落到具体项目上选型可以按「输出形态」来分输出是标签或向量走 Encoder-Only输出是定长结构化文本、输入输出长度接近可以考虑 Encoder-Decoder输出是开放式文本或需要多轮交互走 Decoder-Only。这个判断不需要纠结太久因为生态和工具链的倾斜已经很明显。误用一是拿 BERT 类模型做生成。双向注意力没有因果约束训练时每个位置都见过后面的词推理时无从「按顺序生成」勉强套一个自回归解码循环效果也远不如同规模 Decoder-Only。误用二是把 Decoder-Only 的最后隐藏状态直接平均池化成句向量。因果掩码下靠前位置的表示几乎不含后文信息直接平均会让句向量偏向开头几个词。常见做法是取最后一个非 padding 位置的隐藏状态或者用专门的对比学习目标再训一轮。误用三是掩码写反。torch.triu得到的是上三角用它当因果掩码会让模型只能看未来、看不到历史训练 loss 会停在一个明显偏高的平台上不动。这个错误代码不报错只能靠打印 mask 矩阵肉眼确认我的习惯是第一次跑新实现时固定打印一个 8×8 的 mask 看一眼。4. 动手搭 LLaMA2Tokenizer、预训练、SFT 的参数怎么定第五章是整份教程最费时间的部分基于 PyTorch 逐层搭一个 LLaMA2 结构的小模型训练自己的 Tokenizer跑通预训练和有监督微调。这一章的现实目标是「在小语料上把全链路走通」而不是训出一个能打的模型。参数设定的思路和一线的做法差别不大只是数量级缩小了。4.1 先训 Tokenizer词表大小与特殊 token 的取舍模型结构可以照着改Tokenizer 却必须自己训。现成的词表是按通用语料切的直接用在垂直领域语料上一个专业术语被切成五六个子词是常事序列长度白白膨胀。from tokenizers import Tokenizer, models, trainers, pre_tokenizers, decoders # byte-level BPE任何字符都能被表示为字节理论上不存在 OOV tokenizer Tokenizer(models.BPE(unk_tokenNone)) tokenizer.pre_tokenizer pre_tokenizers.ByteLevel(add_prefix_spaceFalse) tokenizer.decoder decoders.ByteLevel() trainer trainers.BpeTrainer( vocab_size32000, # 与 LLaMA2 对齐中文为主可加到 40000~50000 min_frequency2, # 低频子串直接丢弃避免词表被长尾占满 special_tokens[pad, bos, eos, unk], initial_alphabetpre_tokenizers.ByteLevel.alphabet(), ) tokenizer.train(files[corpus.txt], trainertrainer) tokenizer.save(my_tokenizer.json) # 验证检查压缩率和特殊 token 是否正确 enc tokenizer.encode(大语言模型的预训练过程需要大量语料。) print(len(enc.ids), enc.tokens[:12])vocab_size的选择直接权衡两件事词表大单条序列短、训练快但嵌入层和输出层参数变多小模型上这部分占比会很夸张词表小参数省但序列变长注意力计算量按平方增长。32K 是 LLaMA2 的取值以中文为主的语料我一般会加到 40K 到 50K能明显降低中文的字级切分比例。min_frequency2是个经验值设成 1 会让词表里塞满只出现一次的子串。initial_alphabet必须传 byte-level 的 256 个字节字符否则训出来的词表在遇到罕见字符时会静默丢字符。训完一定要做两件事第一看一眼压缩率原始字符数除以 token 数中文语料通常在 1.5 到 2.0 之间低于 1.2 说明词表没学到东西第二把bos、eos、pad的 id 打印出来后面写 collate 函数时会反复用到写错一个就会导致 padding 被当成正文参与 loss。4.2 预训练循环batch、梯度累积与学习率的组合个人开发者最现实的约束是显存。一张 24G 卡跑 1B 级别的模型序列长度 1024、batch size 也就一两条梯度累积是绕不开的手段。教程里的训练循环骨架值得照抄但几处参数要按自己的硬件改。import torch from torch.optim import AdamW from torch.optim.lr_scheduler import LambdaLR model.train() optimizer AdamW(model.parameters(), lr3e-4, betas(0.9, 0.95), weight_decay0.1) accum_steps 8 # 显存放得下多少就设多少等效 batch micro_batch * accum * 卡数 warmup_steps 2000 total_steps 100000 def lr_lambda(step): if step warmup_steps: return step / max(1, warmup_steps) # 线性 warmup # 余弦衰减到峰值的 10% progress (step - warmup_steps) / max(1, total_steps - warmup_steps) return 0.1 0.9 * 0.5 * (1 torch.cos(torch.tensor(progress * 3.1415926))) scheduler LambdaLR(optimizer, lr_lambda) for step, batch in enumerate(loader): input_ids batch[input_ids].cuda() labels input_ids.clone() # 预训练阶段输入即标签 # 模型内部做 shiftlogits[:, :-1] 对 labels[:, 1:] out model(input_idsinput_ids, labelslabels) loss out.loss / accum_steps # 关键除累积步数否则等效学习率翻倍 loss.backward() if (step 1) % accum_steps 0: torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) optimizer.step() scheduler.step() optimizer.zero_grad(set_to_noneTrue) # 比 zero_grad() 省一次显存写几个参数值得单独说。betas(0.9, 0.95)是 LLaMA 系列的常用配置第二个 beta 比默认的 0.999 小意味着二阶矩估计窗口更短对梯度突变反应更快大规模训练里更稳。weight_decay0.1只加在权重矩阵上LayerNorm 和 bias 不加这一点在实现上要么用参数分组要么手动过滤。accum_steps除 loss 是最容易漏的一步如果忘了除反向传播的梯度是累积 batch 的 N 倍等效学习率被放大表现为 loss 剧烈震荡甚至发散而代码本身完全合法。clip_grad_norm_的 1.0 是通用起点训小模型时可以放到 1.0训大模型常见 0.5 到 1.0。还有一个常被忽略的点预训练阶段的labels要和input_ids完全一致shift 操作在模型内部完成。如果自己手动错开一位再传进去模型内部又 shift 一次loss 会算在错两位的偏移上训练能跑但效果奇差排查时很难想到。4.3 SFT 的 loss mask只对回答算 loss有监督微调的核心不在模型在数据构造。指令部分不能让模型学否则推理时它会热心地帮你把问题再复述一遍。做法是把指令部分的 label 置为 -100PyTorch 的交叉熵会自动忽略这个值。IGNORE_INDEX -100 def build_sft_example(tokenizer, instruction, response, max_len1024): # 指令模板要固定推理时用同一个模板否则分布不匹配 prompt fbos用户{instruction}\n助手 prompt_ids tokenizer.encode(prompt, add_special_tokensFalse).ids resp_ids tokenizer.encode(response eos, add_special_tokensFalse).ids input_ids (prompt_ids resp_ids)[:max_len] labels ([IGNORE_INDEX] * len(prompt_ids) resp_ids)[:max_len] # 截断可能把 labels 截成全部 -100这种样本要丢掉 if all(l IGNORE_INDEX for l in labels): return None return {input_ids: input_ids, labels: labels}IGNORE_INDEX -100是 PyTorchCrossEntropyLoss的默认忽略值别改成别的数字。add_special_tokensFalse是必须的否则每次 encode 都会重复加bos一条样本里出现两个起始符模型会学乱。截断逻辑里那个「labels 全被截掉」的检查很容易漏漏了的后果是这些样本产生零 loss看起来正常实际上白白消耗算力还会让梯度里混入全零项。模板字符串必须和推理时完全一致多一个空格、少一个换行模型的表现都可能掉一截这也是很多人抱怨「微调完反而不如微调前」的常见原因。4.4 切到 Transformers 与 LoRAtarget_modules 怎么选第六章把 PyTorch 手写版换成业界框架用transformers加peft做 LoRA 微调。这一步的意义是把前面的理解迁移到能直接上生产的工具链上。from transformers import AutoModelForCausalLM, AutoTokenizer from peft import LoraConfig, get_peft_model model AutoModelForCausalLM.from_pretrained( 本地模型目录, torch_dtypebfloat16, device_mapauto ) lora_cfg LoraConfig( r16, lora_alpha32, # 缩放系数 alpha/r 2 target_modules[q_proj, k_proj, v_proj, o_proj, gate_proj, up_proj, down_proj], lora_dropout0.05, biasnone, task_typeCAUSAL_LM, ) model get_peft_model(model, lora_cfg) model.print_trainable_parameters() # 通常应打印出不到 2% 的可训练参数r16是通用起点任务越接近预训练分布、需要的 r 越小如果做风格迁移或垂直领域适配可以试到 32 或 64。lora_alpha除以r得到的缩放系数决定了 LoRA 分支的相对强度常见取 1 到 2很多人只调r不调alpha结果换了r之后效果忽好忽坏其实是缩放系数被顺带改了。target_modules是最容易出问题的一处只加q_proj、v_proj是早期论文的省钱配置参数量少但效果打折全量加七个投影层是现在的常见做法参数多一点但收敛明显更稳。用print_trainable_parameters()确认比例如果打出来是 0%说明模块名没匹配上模型结构里的名字可能是Wqkv或者带前缀需要先打印model结构对一遍。5. 训练工程避坑显存、精度、断点与 LoRA 的五个翻车点理论对上了、代码抄完了真正卡人的是训练跑起来之后那些不报错但结果不对的情况。下面五条是我在按这份教程复现时踩过或者见人踩过的按「现象、原因、解决」写清楚。5.1 五个高频问题的排查记录现象一第一步就 OOM但模型参数量明明不大。原因往往不在模型本身而在输出层的 logits。词表 32K、序列 2048、batch 4中间张量是[4, 2048, 32000]bfloat16 下大约 500MB反向传播还要再存一份加上注意力矩阵显存瞬间翻倍。解决办法是开启梯度检查点gradient_checkpointing_enable()用约 30% 的速度换显存同时把 batch 降到 1、用梯度累积补回来。另一个方向是检查有没有哪一层被误设成 float32混合精度里只要有一层是 fp32整条链路都会被拖回高精度。现象二换成 bf16 之后直接报错提示当前设备不支持。这不是代码问题是硬件问题。bf16 需要计算能力 8.0 以上的卡老一些的消费级卡只支持 fp16。解决办法是回退到 fp16 并开启torch.cuda.amp.GradScaler因为 fp16 的动态范围窄梯度小于 6e-8 会下溢成 0loss scaling 就是用来把梯度放大再缩回来的。这条属于没得选的硬约束先查硬件再定精度别在配置上反复试。现象三断点续训之后 loss 曲线出现台阶。原因通常是只存了模型和优化器没存 dataloader 和 scheduler 的状态。恢复时 dataloader 从第 0 条重新开始模型却已经在第 N 步等于把前面的数据又学了一遍loss 会短暂下降然后异常scheduler 从 warmup 重启则会让学习率突然变大。解决办法是把scheduler.state_dict()、当前的 step 数、以及torch.get_rng_state()一起存进 checkpoint恢复时逐项 load 回去。分布式训练还要存 sampler 的 epoch让每个 rank 从正确的分片继续。现象四LoRA 训完模型输出变得重复、啰嗦。先查target_modules是否只覆盖了q_proj、v_proj覆盖不全时模型只能在一个受限的子空间里调整容易过拟合到训练集的句式。再查学习率LoRA 的常用区间是 1e-4 到 3e-4比全参微调高一个数量级如果误用了全参的 2e-5训练会慢到看不出变化很多人以为是配置错实际上是步数不够。第三个要查的是数据SFT 数据里如果有大量超短回答混着长回答模型会学到「看情况决定说多长」表现就是不稳定。现象五把多条短样本拼成定长序列后模型开始胡言乱语。这是 packing 的经典副作用。多条样本拼成一条 2048 的序列如果在样本边界处没有重置注意力掩码后一条样本的 token 会attend到前一条样本的内容模型学到跨文档的虚假关联。解决办法是用 document-level mask在每条样本的边界处把注意力堵住简单一点的做法是拼的时候插入eos并在 collate 里构造 block-diagonal 的 mask。如果嫌麻烦最稳的方案是不 packing用 padding 加attention_mask代价是算力利用率低一些。提示训练脚本里加一条固定检查——第一次迭代后打印 loss、grad_norm、学习率三个数。loss 应该是ln(vocab_size)附近32K 词表约 10.4grad_norm 在 1 附近学习率等于 warmup 后的值。三个数有一个明显不对说明配置层面就有问题不用等训练跑完再回头查。6. 把训练产物接上 RAG 与 Agent一条能验证的最小链路模型训完只是半成品教程最后一章把 RAG 和 Agent 接了上去这部分我建议不要跳过因为它决定了你训的模型到底能不能用。RAG 的最小链路是四步文档切块、向量化入库、按问题检索 top-k、把检索结果拼进 prompt 再生成。切块大小我一般取 300 到 500 字重叠 50 字太大召回不准、太小上下文断裂。检索环节真正要看的指标不是生成结果好不好看而是 recallk——把标准答案所在的那一块能不能进 top-5这个指标不达标后面怎么调 prompt 都是白费。Agent 那部分教程给的是简化实现核心是把工具调用表达成模型能生成的固定格式比如输出一段 JSON 描述函数名和参数外部解析后执行再把结果塞回上下文。这条路线的成败几乎全在格式稳定性上模型稍微跑偏一点JSON 就解析失败。我的做法是先用几条样本做约束解码验证确认模型在给定模板下能稳定输出合法结构再去接真实工具否则会在「解析失败、重试、再失败」的循环里耗掉一整天。验证环节给一个可操作的习惯每次训完模型固定跑三个检查。第一用训练集里没出现过的十条指令看输出格式是否合规第二跑一个 200 条的小评测集人工看前 20 条剩下的看平均长度和拒绝率第三RAG 链路单独测 recall5和生成质量分开看。三项里哪项异常问题就定位在哪一段比笼统地说「效果不好」有用得多。血泪经验是我曾经跳过第一项直接上 RAG结果检索明明命中了正确段落生成出来的答案还是错的排查了半天才发现是 SFT 阶段模板里的冒号用了全角、推理时用了半角模型没见过这个前缀直接进入自由续写模式。从那以后我每次换模板都会先把编码后的 token 打印出来肉眼对一遍再开始训练。希望帮到你。本文还有配套的精品资源点击获取
企业数字化 ERP 产品动态
相关推荐
GEO方案实战:从SEO排名到AI答案引用的四层架构与验收指南 简介:这份文档面向数字营销、内容运营与SEO从业者,尤其是希望在AI搜索时代抢占流量先机的中小企业主和内容创作者,系统讲解GEO(生成式引擎优化)的概念、原理与落地方法。内容从GEO与传统SEO的差异切入,解析… · 2026/11/3 2:56:29
SAP可用性检查实战:静态与动态规则、补货提前期及排查避坑 简介:《SAP可用性检查应用测试.docx》是一份面向SAP顾问、供应链计划及MM/SD/PP模块学习者的专题文档,聚焦静态可用性检查与动态可用性检查的区别、配置思路与测试验证,帮助读者理解库存承诺、预留与采购计划对可用性的影响。压缩包仅含1个do… · 2026/11/3 2:56:29
AI产品落地判断清单:从需求筛选到验收全流程 简介:《产品经理的AI实战》读书笔记.pdf 面向关注人工智能产品化与商业落地的产品经理、创业者及技术管理者,帮助读者在有限时间内把握原著三部分13章的核心框架。PDF将216页内容压缩为28页笔记,围绕技术商业的成功要素、X技术的不确定性、技… · 2026/11/3 2:56:29
2K1000裸机移植Coremark v1.01实战:从启动代码到Cache一致性调优 1. 项目概述:为什么在2K1000上跑Coremark v1.01不是“刷个分”那么简单你可能在某次嵌入式系统调试日志里见过这行输出:“CoreMark 1.01 / 2K10001.0GHz: 2846.33 pts”,也或许刚在国产处理器技术论坛看到有人贴出带缓存配置截图的跑分结果&a… · 2026/11/3 7:07:53
多微电网拓扑优化:矩阵建模与差分进化算法实践 1. 多微电网拓扑优化:从工程痛点出发作为一名长期从事电力系统优化的工程师,我深刻理解多微电网(MMG)拓扑设计面临的现实挑战。当你在工业园区或偏远地区部署微电网群时,最头疼的问题莫过于:如何在数十个甚… · 2026/11/3 7:07:53
CodeGraph:嵌入式代码知识图谱构建与SQLite实践 1. 这不是又一个代码索引工具:CodeGraph 是嵌入式开发者的“代码导航仪”你有没有在调试一个运行在 AM335x 或 STM32H7 上的裸机驱动时,突然卡住——明明记得某个 DMA 配置函数在drivers/serial/下改过,但翻遍drivers/目录却找不到它… · 2026/11/3 7:07:53
TensorFlow安装与部署:从版本兼容到生产级架构全解析 1. 这不是“装个库”那么简单:TensorFlow到底在解决什么问题?你搜“tensorflow安装”,页面跳出的全是pip install、conda install、GPU驱动版本匹配、CUDA Toolkit版本号……但真正卡住人的,从来不是那行命令敲不敲得下去… · 2026/11/3 7:07:53
ROS2 Nav2真机导航实战:视觉SLAM+语义避障一小时跑通 1. 这不是“教具演示”,是真正在机器人上跑通的导航实战你点开这个标题,大概率不是想看PPT里画的漂亮流程图,也不是想听“具身智能是AI与物理世界交互的下一代范式”这种教科书定义。你真正想要的,是把一段代码烧进小车主控板、让… · 2026/11/3 7:07:53
3DTiles与OSGB格式转换技术解析与实践 1. 3DTiles与OSGB格式概述在三维地理信息领域,3DTiles和OSGB是两种广泛使用的三维模型数据格式。3DTiles是由Cesium团队提出的开放标准,专为流式传输和渲染大规模三维地理数据设计。它采用分块、分层级的数据组织方式,支持LOD(细节… · 2026/11/3 7:06:53
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 6:04:24
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 5:52:46
STM32F411RE与MAX14431实现SPI隔离:工业现场抗共模干扰实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59
ESP32+大模型AI硬件开发:从Demo到产品的8个工程问题 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59
基于MAX14431BAEE+与PIC18F85J10的SPI隔离方案设计与实践 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 6:04:24
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 5:52:46