资讯动态

七天跑通AI大模型:Transformer、LoRA、RAG与Agent工程链路

发布时间:2026/10/28 2:13:17 来源:尧图企业网站定制
七天时间能不能入门 AI 大模型先把“入门”这个词的定义收紧一点不是让你七天造一个 GPT也不是让你背完所有论文而是在七天里打通一条能跑起来的完整链路——读懂 Transformer 的张量流向、跑通一次小规模预训练、用 LoRA 微调出一个属于自己领域的模型、写出稳定可控的结构化提示词、搭一套能查资料再回答的 RAG、做出一个会自己调用工具的 Agent最后把这些能力封装成接口给业务方用。这条路线最值得写的地方不是知识点堆得多而是每一步都留了一个“最小可运行产物”。你今天学的注意力机制晚上就能用几十行 PyTorch 跑出一个能出字的模型你今天看的 LoRA 原理明天就能挂在自己的数据上训练出一个 adapter 文件你写的提示词模板后天就能被 RAG 和 Agent 复用。学习大模型最怕的是看了两周视频一个能跑的 demo 都没有那样知识是悬空的遇到真实问题立刻崩。本文不会给你灌鸡汤而是把这条七天路线拆成可执行的动作环境怎么准备、显存怎么估算、每阶段该写什么代码、跑完怎么判断自己真的学会了、接口和批量任务怎么封装、遇到 OOM 和 loss 不降该怎么排查。核心能力速览、实操代码、效果验证标准、常见坑位都会写清楚。适合三类读者一是刚接触大模型、想把原理和工程串起来的后端或算法同学二是有 Python 基础、想在自己业务数据上做微调和 RAG 的开发者三是已经在用大模型 API、但搞不清底层发生了什么、调 prompt 全靠玄学的工程师。如果你完全没有 Python 和 Linux 基础建议先补两周基础再来否则七天会变成七天看报错。1. 七天路线核心能力速览在动手之前先把整条路线的产出物和门槛摆清楚。很多人学不下去不是因为难而是因为不知道每天结束的时候自己应该手上有什么东西。下表把七天拆成五个阶段每个阶段都标注了产出物和硬件门槛方便你按自己的机器条件做取舍。能力项说明路线类型大模型原理 工程落地的一体化学习路径覆盖内容Transformer 结构、预训练、LoRA 微调、提示工程、RAG、Agent每天产出一个可运行的脚本或一份可复用的配置文件推荐硬件微调阶段建议有独立显卡纯原理 demo 和 RAG 用 CPU 也能跑显存占用取决于模型规模、序列长度、batch size 和是否使用量化需按实际环境实测支持平台Linux 优先Windows 可用 WSL 或原生 Python 环境启动方式命令行脚本 可选 Web 服务FastAPI / Uvicorn是否支持 API是最后阶段会把能力封装成 HTTP 接口是否支持批量任务是通过目录扫描或任务队列实现适合场景个人学习、团队内训、业务侧小模型落地验证需要强调的一点是这张表里唯一不能含糊的是显存。同一份微调代码序列长度从 512 拉到 2048显存占用可能翻几倍batch size 从 1 加到 8也可能直接 OOM。所以后面每一节讲参数时我都会告诉你先从小值开始试跑通了再往上加而不是一上来就抄别人的配置。1.1 五个阶段的先后顺序不能乱顺序是有依赖关系的。Transformer 是地基你不理解注意力的 QKV 是怎么算的后面看 LoRA 为什么只训练低秩矩阵就会一头雾水预训练是最能暴露“数据质量决定模型上限”这件事的环节跳过它直接微调你会误以为微调是万能的提示工程看起来最简单但它决定了你后面 RAG 和 Agent 的输出稳不稳定RAG 解决的是“模型不知道你公司文档”的问题Agent 解决的是“模型只能说话不能干活”的问题。所以这七天不是七个平行的知识点而是一条逐步加码的流水线。每天结束时建议做一件事把当天的代码和结论写进一个notes.md写下“我跑通了什么、卡在哪里、怎么解决的”。七天之后这份笔记比任何教程都值钱因为它是你自己踩过的坑。1.2 每天的时间分配建议时间分配上我的建议是原理 30%、编码 50%、验证和记录 20%。很多人会把 80% 时间花在看讲解上结果动手时连环境都装不明白。更高效的做法是带着问题去看原理先跑代码跑出反直觉的结果再回去查为什么。具体到每天Day1 和 Day2 啃 TransformerDay3 跑预训练和数据处理Day4 做微调Day5 提示工程加 RAG 起步Day6 把 RAG 做完整并做评估Day7 做 Agent 和服务化。如果你的时间更紧可以把 Day1-2 压缩成一天但注意力机制那部分代码一定要亲手敲一遍不能复制粘贴。2. 适用人群与使用边界这条路线不是给所有人准备的。它适合有 Python 基础、能看懂函数和类、知道什么是虚拟环境、能用命令行跑脚本的人。如果你连pip install都经常失败先花两天把环境问题解决干净否则后面每一步都会卡在依赖上学习节奏会被彻底打乱。它也不适合想“七天做出一个商用产品”的人。七天能做出的是可运行的原型一个能对领域问题给出像样回答的微调模型、一个能引用文档出处的问答服务、一个能调两个工具的 Agent。原型和产品的差距在数据治理、评测体系、并发能力和监控告警上这些不是七天能补齐的但七天能让你知道自己缺什么。2.1 能力边界要说清楚微调不是万能的。如果你的目标是让模型记住大量事实性知识微调的效果通常不如 RAG因为微调更擅长改变“说话方式和任务格式”而不是精确记忆。反过来说如果你要模型稳定输出某种 JSON 结构、或者用特定口吻写文案微调比堆提示词靠谱得多。这个判断在做 Day4 之前就要想清楚否则你会花一天训练出一个还不如 RAG 的模型。RAG 也不是万能的。它对切分策略、embedding 模型选型、召回数量、重排逻辑都非常敏感。同一份文档切分粒度差一倍答案质量可能差一个档次。所以后面我会专门讲怎么验证检索质量而不是只看最终回答像不像。Agent 更不是万能的。工具调用失败、循环不收敛、参数幻觉这三个问题会消耗掉你大量调试时间。稳妥的做法是先把工具数量控制在两三个把每一步的输入输出打日志确认链路稳定后再加复杂度。2.2 数据与合规边界如果你打算用公司内部文档做 RAG动手前必须确认这些文档是否允许进入本地向量库、是否允许被模型读取、日志里是否会残留敏感字段。如果你的微调数据包含用户对话、个人信息、合同条款必须做脱敏处理并且在测试环境验证不要直接在生产数据上跑。涉及人脸、声音、肖像、版权素材的生成类任务必须有明确授权。这条不是形式主义一旦出问题技术方案再好也没有意义。本文的实操建议全部在本地测试环境完成样本数据建议用公开数据集或自己构造的假数据。3. 环境准备与前置条件环境这一步决定了你后面七天是顺畅还是崩溃。很多人上来就pip install一堆包装完发现 CUDA 版本对不上torch 装成了 CPU 版训练时慢十倍。所以先把检查清单过一遍比装完再返工省时间。3.1 硬件与系统检查先确认你的显卡和驱动状态。Linux 下用nvidia-smiWindows 下在命令行里同样可用。重点看三件事驱动版本、CUDA 版本、显存总量。显存决定你能玩多大的模型驱动和 CUDA 版本决定你装哪个版本的 PyTorch。# 查看显卡与驱动状态 nvidia-smi # 查看系统内存与磁盘余量模型文件和数据集很吃磁盘 free -h df -h # 确认 Python 版本 python --version如果nvidia-smi报错或者根本找不到命令说明驱动没装好这时候装任何深度学习框架都是白费。先把驱动搞定再往下走。如果没有独立显卡也不要放弃Transformer 原理 demo、提示工程、RAG 的大部分环节用 CPU 都能跑只是微调阶段会很慢可以改用小模型或者租用临时算力。磁盘空间要留够。一个中等规模的基座模型动辄几个 GB 到十几个 GB加上数据集、checkpoint、向量库文件建议至少留出 50GB 以上空闲空间并且把模型目录和数据目录分开放避免哪天清理缓存把训练好的权重删了。3.2 Python 环境与依赖隔离强烈建议用 conda 或 venv 建独立环境。不同项目对 torch、transformers 的版本要求经常冲突混装是灾难的开始。# 创建并激活独立环境 conda create -n llm7d python3.10 -y conda activate llm7d # 安装核心依赖版本请根据本机 CUDA 情况选择官方推荐组合 pip install torch transformers datasets accelerate peft pip install sentence-transformers faiss-cpu pip install fastapi uvicorn pydantic requests这里的版本号我故意没写死因为 PyTorch 和 CUDA 的匹配关系会随官方发布变化正确做法是去 PyTorch 官网按你的 CUDA 版本选对应安装命令。装完之后一定要验证import torch print(torch:, torch.__version__) print(cuda available:, torch.cuda.is_available()) print(device count:, torch.cuda.device_count()) if torch.cuda.is_available(): print(device name:, torch.cuda.get_device_name(0)) print(total memory GB:, round(torch.cuda.get_device_properties(0).total_memory / 1024**3, 2))如果cuda available是 False但你确实有显卡那基本就是装成了 CPU 版本卸载重装。这一步不解决后面微调会慢到让你怀疑人生。3.3 目录结构与模型文件管理建议从第一天就建立清晰的目录结构。这不是洁癖而是七天之后你会有一堆模型、数据、输出混在一起根本找不到。llm7d/ ├── data/ # 原始语料与指令数据 ├── models/ # 基座模型权重 ├── adapters/ # LoRA 输出 ├── outputs/ # 推理结果与批量任务产物 ├── notebooks/ # 试验脚本 ├── src/ # 可复用代码 └── notes.md # 每日记录模型下载建议用官方提供的下载方式或者可信镜像站下完校验文件完整性。如果下载中断导致权重文件损坏加载时会报各种奇怪的 key 不匹配错误很难排查。稳妥做法是下载后先跑一次最小推理确认模型能正常出字再进下一步。4. 阶段一Transformer 原理与最小复现Day1 和 Day2 的目标只有一个你能不看教程从零写出一个多头自注意力层并且能解释清楚每个张量的形状。这两天的价值不在于背公式而在于建立“形状直觉”。大模型调试 90% 的问题最后都归结为形状对不上或者维度搞反了。4.1 从张量形状开始建立直觉输入是(batch, seq_len, d_model)。经过 Q、K、V 三个线性层之后形状还是(batch, seq_len, d_model)然后拆成多头变成(batch, n_head, seq_len, head_dim)。注意力分数是 Q 和 K 的转置相乘得到(batch, n_head, seq_len, seq_len)这个矩阵就是每个 token 对其他 token 的关注权重。乘上 V 之后回到(batch, n_head, seq_len, head_dim)再拼回(batch, seq_len, d_model)。把这几个形状在纸上画一遍比看十篇讲解都管用。因为一旦形状清楚了缩放点积注意力那个1/sqrt(d_k)为什么存在也就明白了点积的方差随维度增长不缩放的话 softmax 会进入饱和区梯度变得极小。4.2 用几十行代码写一个能跑的注意力层下面这段代码是完整的因果自注意力实现可以直接复制运行。它同时支持训练和推理时的掩码需求是所有主流大模型注意力层的简化版。import math import torch import torch.nn as nn import torch.nn.functional as F class CausalSelfAttention(nn.Module): def __init__(self, d_model: int, n_head: int, block_size: int, dropout: float 0.1): super().__init__() assert d_model % n_head 0, d_model 必须能被 n_head 整除 self.d_model d_model self.n_head n_head self.head_dim d_model // n_head self.block_size block_size # 一个线性层同时产出 Q K V工程上更高效 self.qkv nn.Linear(d_model, 3 * d_model, biasFalse) self.proj nn.Linear(d_model, d_model, biasFalse) self.attn_drop nn.Dropout(dropout) self.resid_drop nn.Dropout(dropout) # 因果掩码保证只能看到左侧上下文 mask torch.tril(torch.ones(block_size, block_size)) self.register_buffer(mask, mask.view(1, 1, block_size, block_size)) def forward(self, x: torch.Tensor) - torch.Tensor: B, T, C x.shape qkv self.qkv(x) # (B, T, 3C) q, k, v qkv.split(self.d_model, dim2) # 各 (B, T, C) # 拆多头: (B, T, C) - (B, n_head, T, head_dim) q q.view(B, T, self.n_head, self.head_dim).transpose(1, 2) k k.view(B, T, self.n_head, self.head_dim).transpose(1, 2) v v.view(B, T, self.n_head, self.head_dim).transpose(1, 2) # 缩放点积注意力 att (q k.transpose(-2, -1)) / math.sqrt(self.head_dim) att att.masked_fill(self.mask[:, :, :T, :T] 0, float(-inf)) att F.softmax(att, dim-1) att self.attn_drop(att) y att v # (B, n_head, T, head_dim) y y.transpose(1, 2).contiguous().view(B, T, C) return self.resid_drop(self.proj(y)) if __name__ __main__: x torch.randn(2, 16, 128) attn CausalSelfAttention(d_model128, n_head4, block_size64) out attn(x) print(输入形状:, tuple(x.shape)) print(输出形状:, tuple(out.shape)) print(输出是否与输入同形:, out.shape x.shape)跑通之后试着把n_head改成 8、把block_size改成 32、把序列长度改成超过block_size看看会不会报错。这种“故意改坏”的练习比顺着跑一遍收获大得多因为真实排错时你就是这么定位问题的。4.3 怎么判断自己真的理解了给自己出三个问题第一如果把因果掩码去掉模型在训练时会变成什么样第二head_dim变小、n_head变多参数量和计算量分别怎么变第三为什么推理时要缓存 K 和 V缓存的形状是什么能顺畅答出这三题Day1-2 就算过关。答不出来的话回去改代码观察输出不要急着往下走因为 Day3 的预训练会直接用到这些概念。5. 阶段二预训练与微调Day3 跑预训练Day4 做微调。这两天是整条路线里最费算力的也是最容易让人产生挫败感的。所以目标一定要设得现实预训练部分你只需要在小语料上跑通训练循环看到 loss 正常下降理解数据是怎么变成 batch 的不要指望在个人设备上训出有实用价值的基座模型。5.1 预训练到底在优化什么语言模型的预训练任务就是“预测下一个 token”。给定一段文本模型看到前 n 个 token要预测第 n1 个。损失函数是交叉熵输入是 logits(B, T, vocab_size)标签是右移一位的 token id。这个任务看似简单但要在足够大的语料上把 loss 压下去模型必须学会语法、事实、推理模式。理解这一点很关键预训练学到的是“分布”不是“指令”。所以基座模型你问它“帮我写个请假条”它可能给你续写一段类似的东西而不是真的按你的要求写。指令跟随能力是后面的指令微调和偏好对齐赋予的。这解释了为什么 Day4 的微调是必要的一步。5.2 小规模语料跑通训练循环下面是一个极简训练循环重点是让你看清楚数据流和 loss 计算而不是追求效果。用一个很小的模型和一段重复文本就能跑起来。import torch import torch.nn as nn from model import MiniGPT # 用 4.2 的注意力层拼装的小模型 device cuda if torch.cuda.is_available() else cpu # 迷你词表真实场景请换成 tokenizer 的 vocab_size vocab_size 1000 block_size 128 model MiniGPT(vocab_sizevocab_size, d_model128, n_head4, n_layer4, block_sizeblock_size).to(device) optimizer torch.optim.AdamW(model.parameters(), lr3e-4) # 假设 tokens 是已经把语料编码成 id 的一维张量 tokens torch.randint(0, vocab_size, (20000,)) def get_batch(batch_size8): ix torch.randint(0, len(tokens) - block_size - 1, (batch_size,)) x torch.stack([tokens[i:i block_size] for i in ix]).to(device) y torch.stack([tokens[i 1:i 1 block_size] for i in ix]).to(device) return x, y model.train() for step in range(300): x, y get_batch() logits model(x) # (B, T, vocab_size) loss nn.functional.cross_entropy( logits.view(-1, vocab_size), y.view(-1) ) optimizer.zero_grad(set_to_noneTrue) loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) optimizer.step() if step % 50 0: print(fstep {step:4d} | loss {loss.item():.4f} f| mem {torch.cuda.memory_allocated()/1024**2:.0f} MB if device cuda else fstep {step:4d} | loss {loss.item():.4f})这段代码里的随机 token 只是为了验证流程跑通loss 会停在一个很高的平台期这是正常的。换成真实语料后你会看到一个更明显的下降曲线。判断训练是否正常看三个信号loss 是否整体下行、是否有突然的尖峰、梯度范数是否爆炸。5.3 微调全量还是 LoRADay4 进入微调。全量微调需要更新所有参数对显存要求高LoRA 只训练低秩旁路矩阵显存占用小得多而且输出的是几十 MB 的 adapter 文件方便切换和分发。学习阶段几乎无脑选 LoRA。微调的第一步是数据格式。指令微调常用三段式指令、输入、输出。数据质量比数量重要得多一千条干净、格式统一、答案准确的样本往往胜过十万条爬来的脏数据。构造数据时要特别注意答案必须是你希望模型学会的那种回答包括长度、语气、格式。import json from datasets import Dataset from peft import LoraConfig, TaskType, get_peft_model from transformers import AutoModelForCausalLM, AutoTokenizer, TrainingArguments, Trainer model_name your-base-model-path # 替换成本地基座模型目录 tokenizer AutoTokenizer.from_pretrained(model_name) model AutoModelForCausalLM.from_pretrained(model_name, device_mapauto) lora_config LoraConfig( task_typeTaskType.CAUSAL_LM, r8, lora_alpha16, lora_dropout0.05, target_modules[q_proj, v_proj], ) model get_peft_model(model, lora_config) model.print_trainable_parameters() def build_dataset(path): rows [] with open(path, r, encodingutf-8) as f: for line in f: item json.loads(line) text ( f### 指令\n{item[instruction]}\n f### 输入\n{item.get(input, )}\n f### 输出\n{item[output]} ) rows.append({text: text}) return Dataset.from_list(rows) def tokenize(batch): return tokenizer(batch[text], truncationTrue, max_length512) ds build_dataset(data/train.jsonl).map(tokenize, batchedTrue) args TrainingArguments( output_diradapters/lora-run1, per_device_train_batch_size1, gradient_accumulation_steps8, num_train_epochs3, learning_rate2e-4, logging_steps10, save_steps200, fp16torch.cuda.is_available(), report_tonone, ) trainer Trainer(modelmodel, argsargs, train_datasetds) trainer.train() model.save_pretrained(adapters/lora-run1-final)这里有几个参数需要重点理解。r是低秩矩阵的秩越大表达能力越强但参数量也越大一般从 8 或 16 起步。lora_alpha是缩放系数常设为r的两倍左右。target_modules决定挂在哪些层上不同模型结构的层名不一样挂错了会完全不生效所以训练开始后一定要看print_trainable_parameters()输出的可训练参数占比正常应该在千分之几到百分之几之间如果是 0 就说明没挂上。5.4 微调效果怎么验证训练完成不等于有效。验证要分三层第一层看 loss 曲线是否平滑下降、有没有过拟合第二层用训练集外的样本做推理看格式是否符合预期第三层做对比测试同一个问题分别问基座模型和微调后模型人工判断差异。这一步最常见的坑是数据泄漏验证集里的样本和训练集重复导致指标虚高。另一个坑是数据格式不一致训练时带了### 输出标记推理时忘了加模型输出就会跑偏。所以推理时的 prompt 模板必须和训练时严格一致建议把模板抽成一个函数训练和推理共用。6. 阶段三提示工程与 RAGDay5 和 Day6 是从“会训模型”转向“会用模型”的转折点。现实工作中能用提示工程和 RAG 解决的问题就不要动用微调因为迭代成本差一个数量级。6.1 提示工程的核心是约束输出提示工程不是玄学它的本质是减少模型的输出空间。一个合格的工程化提示词通常包含四部分角色和任务定义、输入数据的明确边界、输出的结构要求、异常情况的处理规则。其中输出结构最关键因为后续代码要解析它。import json from pydantic import BaseModel, ValidationError class Answer(BaseModel): conclusion: str evidence: list[str] confidence: int PROMPT_TEMPLATE 你是一个文档问答助手。请严格根据下面提供的资料回答问题。 资料 {context} 问题{question} 要求 1. 只使用资料中的信息不要编造。 2. 如果资料不足以回答conclusion 写资料不足evidence 留空数组。 3. confidence 取值 0-100表示你对答案的把握。 4. 只输出 JSON不要输出任何解释文字。 输出格式 {{conclusion: ..., evidence: [...], confidence: 0}} def parse_and_validate(raw: str) - Answer | None: text raw.strip().strip() if text.startswith(json): text text[4:].strip() try: return Answer(**json.loads(text)) except (json.JSONDecodeError, ValidationError) as e: print(解析失败:, e) return None写完提示词一定要做压力测试给空资料、给矛盾资料、给超长资料、给和问题完全无关的资料看模型是不是还能守住格式。如果它在某类输入上崩了就把那类情况写成一条规则补进提示词这比反复改措辞有效得多。6.2 RAG 的链路拆解RAG 的完整链路是文档加载 → 切分 → 向量化 → 存入向量库 → 查询向量化 → 相似度检索 → 拼装上下文 → 生成回答。每个环节都有坑但坑最深的是切分和检索质量。切分粒度要匹配你的问题类型。如果你的问题经常需要跨段落综合切太小会导致上下文断裂如果切太大检索精度会下降因为一个块里混了太多主题。稳妥做法是先按结构切标题、段落再按长度兜底并且保留一定的重叠。import numpy as np from sentence_transformers import SentenceTransformer # 用轻量 embedding 模型实际选型需按语言和领域测试 encoder SentenceTransformer(your-embedding-model-path) def chunk_text(text: str, size: int 400, overlap: int 80) - list[str]: chunks [] start 0 while start len(text): end min(start size, len(text)) chunks.append(text[start:end]) if end len(text): break start end - overlap return chunks def build_index(docs: list[str]) - np.ndarray: vecs encoder.encode(docs, normalize_embeddingsTrue, batch_size32, show_progress_barTrue) return vecs def retrieve(query: str, docs: list[str], vecs: np.ndarray, top_k: int 4): q encoder.encode([query], normalize_embeddingsTrue)[0] scores vecs q idx np.argsort(-scores)[:top_k] return [(docs[i], float(scores[i])) for i in idx]这段代码用矩阵点积做余弦相似度好处是零依赖、可调试。数据量上到十万级再考虑引入专门的向量数据库小规模阶段用 numpy 完全够用而且你能清楚看到每一步发生了什么。6.3 检索质量怎么验证很多人只验证最终回答这是不够的。正确的做法是把检索和生成分开评估先准备一批带标准答案出处的问题只看 top_k 召回里有没有包含正确答案所在的块这叫召回率再看生成阶段是否忠实地使用了召回内容这叫忠实度。召回率上不去先查切分切分没问题就换 embedding 模型换了还不行就加关键词检索做混合召回。忠实度上不去通常是提示词约束不够强或者 top_k 太大导致模型被无关内容干扰试着把 top_k 从 8 降到 3 看看。一个特别常见的坑是chunk 里缺少标题信息。比如文档里第 5 节的段落被单独切出来里面全是“该方法”“上述步骤”之类的指代向量化之后语义非常模糊。解决办法是在每个 chunk 前面拼接它所属的标题路径这一个小改动往往能带来明显的召回提升。7. 阶段四Agent 与工具调用Day7 做 Agent。Agent 的本质是把大模型当成决策器让它在“思考 → 调工具 → 看结果 → 再思考”的循环里推进任务。听起来高级但最小实现非常朴素一个 while 循环加一套约定好的输出协议。7.1 最小 Agent 闭环关键设计是让模型输出结构化指令代码负责执行。不要让模型直接生成代码去 exec那既不安全也不稳定。工具表要用 JSON Schema 描述清楚参数类型模型才知道怎么填。import json TOOLS { search_docs: { desc: 在内网文档中检索返回最相关的若干片段, params: {query: string}, }, calculator: { desc: 计算四则运算表达式, params: {expression: string}, }, } def run_tool(name: str, params: dict) - str: if name search_docs: return str(retrieve(params[query], docs, vecs, top_k3)) if name calculator: return str(eval(params[expression], {__builtins__: {}})) return f未知工具: {name} def agent_loop(question: str, max_steps: int 5) - str: history [f问题{question}] for step in range(max_steps): prompt build_agent_prompt(TOOLS, history) raw call_llm(prompt) # 替换成你的模型调用 try: action json.loads(raw) except json.JSONDecodeError: history.append(上一轮输出不是合法 JSON请严格按格式输出。) continue if action.get(type) final: return action[answer] if action.get(type) tool: name action[name] result run_tool(name, action.get(params, {})) history.append(f调用 {name} 返回{result[:800]}) else: history.append(未知动作类型请重新输出。) return 达到最大步数仍未得出结论这段代码里有几个工程细节值得注意。第一max_steps必须有否则模型可能无限循环烧钱。第二工具返回内容要截断不然上下文会迅速膨胀。第三解析失败时不要把原始输出直接丢回去而是给一句明确的纠正指令。第四eval只在这种受限场景下使用生产环境要换成安全的表达式解析器。7.2 Agent 调优的重点在哪Agent 出问题八成不是模型不行而是工具描述写得太模糊。比如“检索文档”这个描述模型不知道什么时候该用、参数该填什么。改成“当问题涉及公司内部制度、流程、产品参数时使用query 填写用户问题的核心关键词”成功率会明显提升。另一个重点是错误处理。工具调用失败、返回为空、超时这些情况模型是不知道的你必须把错误信息作为观察结果喂回去让它决定是换个查询词还是直接放弃。一个健壮的 Agent 应该有明确的重试上限和降级策略比如连续两次检索无结果就直接用已有信息作答而不是死磕。8. 接口服务化、批量任务与性能观察最后一步把前面所有能力封成接口。这一步是把学习成果变成可用工具的分水岭也是很多人忽略的环节。8.1 用 FastAPI 封装推理服务服务化的核心是三个接口单条推理、批量任务提交、健康检查。单条推理要求低延迟批量任务要求能排队和重试健康检查用于确认模型已经加载完成。from fastapi import FastAPI, HTTPException from pydantic import BaseModel app FastAPI(titlellm7d-service) MODEL_READY False class GenerateReq(BaseModel): prompt: str max_new_tokens: int 256 temperature: float 0.7 class GenerateResp(BaseModel): text: str tokens: int app.on_event(startup) def load_model(): global MODEL_READY # 在这里加载 tokenizer 和模型加载完置 True MODEL_READY True app.get(/healthz) def health(): return {ready: MODEL_READY} app.post(/generate, response_modelGenerateResp) def generate(req: GenerateReq): if not MODEL_READY: raise HTTPException(status_code503, detail模型未就绪) if len(req.prompt) 8000: raise HTTPException(status_code400, detail输入过长) out call_llm(req.prompt, max_new_tokensreq.max_new_tokens, temperaturereq.temperature) return GenerateResp(textout, tokenslen(out))启动命令很简单uvicorn app:app --host 127.0.0.1 --port 8000 --workers 1注意--workers不要随便调大。模型在显存里只加载一份多 worker 会重复加载导致 OOM。并发能力靠单进程内的异步或批量推理来提升而不是靠多进程。这一点和传统 Web 服务的直觉相反很多人在这里踩坑。8.2 批量任务的目录设计批量任务最简单可靠的实现是“输入目录 输出目录 状态文件”不要一上来就上消息队列。每个任务一个 JSON 文件处理完写结果并记录状态失败的任务单独落一个错误文件方便重跑。{ input_dir: ./data/batch_inputs, output_dir: ./outputs/batch_run1, failed_dir: ./outputs/batch_run1_failed, batch_size: 4, max_retries: 2, overwrite: false, log_file: ./outputs/batch_run1.log }批处理脚本要做三件事跳过已完成的、捕获单条异常不中断整体、记录耗时。最后一点经常被忽略但它是你优化性能的唯一依据。import json, time, traceback from pathlib import Path cfg json.loads(Path(batch.json).read_text(encodingutf-8)) in_dir, out_dir Path(cfg[input_dir]), Path(cfg[output_dir]) fail_dir Path(cfg[failed_dir]) out_dir.mkdir(parentsTrue, exist_okTrue) fail_dir.mkdir(parentsTrue, exist_okTrue) for fp in sorted(in_dir.glob(*.json)): target out_dir / fp.name if target.exists() and not cfg[overwrite]: continue start time.time() try: item json.loads(fp.read_text(encodingutf-8)) result call_llm(item[prompt]) target.write_text(json.dumps( {id: fp.stem, result: result, elapsed: round(time.time() - start, 2)}, ensure_asciiFalse), encodingutf-8) except Exception: (fail_dir / fp.name).write_text(traceback.format_exc(), encodingutf-8)8.3 性能观察怎么做显存占用要分阶段看模型刚加载完是一个值处理一条短输入又是一个值处理长输入或者批量推理时还会涨。观察方法就是在前面的训练循环里用torch.cuda.memory_allocated()和torch.cuda.max_memory_allocated()后者能捕捉峰值。影响性能的主要变量有四个序列长度、batch size、生成的最大 token 数、是否使用量化。其中序列长度是平方级影响注意力的长度翻倍注意力计算量约翻四倍这是最容易失控的变量。降低显存的手段包括减小 batch size 改用梯度累积、缩短序列长度、开启混合精度、使用量化加载、及时del中间张量并清空缓存。CPU 和 GPU 的差异在推理阶段尤其明显。小模型在 CPU 上可能勉强能跑但延迟会高一个数量级RAG 的 embedding 环节在 CPU 上做批量编码也是可接受的只是耗时更长。所以如果硬件有限优先把 GPU 留给生成和训练embedding 可以放 CPU。9. 常见问题与排查方法七天里你会遇到大量报错绝大多数集中在下面这些类型。提前知道排查顺序能省下大量时间。问题现象可能原因排查方式解决方案torch.cuda.is_available()为 False装成了 CPU 版或驱动不匹配打印 torch 版本与 CUDA 版本卸载后按官方命令重装加载模型报 key 不匹配权重文件损坏或版本不兼容校验文件大小与哈希重新下载完整权重训练时 CUDA out of memorybatch 过大、序列过长、未清缓存打印max_memory_allocated降 batch、开梯度累积、缩短序列loss 长时间不下降学习率过大或过小、数据有问题观察前 100 步曲线与样本调学习率、检查数据编码loss 变成 NaN学习率过大、精度溢出看梯度范数是否爆炸降学习率、加梯度裁剪、改 fp32可训练参数为 0target_modules名字写错看print_trainable_parameters打印模型层名后重新配置微调后输出格式混乱推理模板与训练模板不一致对比两处拼接字符串抽公共函数强制统一RAG 答非所问切分粒度不当或 embedding 不匹配单独看 top_k 召回内容调切分、换模型、加混合检索检索结果里全是无关片段缺少标题上下文、top_k 过大打印召回文本拼接标题路径、减小 top_kAPI 返回 503模型尚未加载完成请求/healthz加启动等待或健康探针批量任务卡住不动单条输入过长或死循环看日志最后一条加超时、限制生成长度服务启动后显存翻倍多 worker 重复加载模型观察进程数固定单 worker端口被占用上一次进程没退出查端口占用进程杀进程或换端口这张表建议贴在显示器边上遇到问题先对号入座。特别提醒两个高频坑一是虚拟环境没激活就装包导致包装到了系统 Python 里代码运行时找不到二是模型路径用了相对路径换个工作目录就加载失败建议统一用绝对路径或者在配置里写清楚基准目录。10. 最佳实践与学习节奏建议七天跑完只是起点真正拉开差距的是后面的迭代方式。这里给几条经过验证的习惯都是能直接减少返工的。第一条永远保留一套最小可运行配置。把能跑通的模型名、参数、数据路径写进一个config.json任何时候出问题都能退回这套配置对照。很多人越调越乱就是因为没有基线。第二条先小后大。序列长度从 128 起batch 从 1 起LoRA 的r从 8 起RAG 的 top_k 从 3 起。每加一档跑一次确认没问题再继续。这条经验能帮你把“不知道哪里出了问题”变成“知道是哪一档加坏了”。第三条评估要可复现。准备一个固定的小测试集每次改动后跑一遍记录指标。没有固定测试集你会陷入“感觉好像变好了”的自我欺骗。测试集不需要大二十条覆盖典型场景就够。第四条数据、模型、输出分目录命名带时间戳。adapters/lora-run1-20260101这种命名看起来啰嗦但当你手上有五个 adapter 分不清哪个是哪个的时候会感谢自己。第五条接口服务默认只监听本机需要对外时再加鉴权和限流。批量任务要有超时和重试上限别让一个坏样本拖垮整批。第六条涉及用户数据、内部文档、人脸声音素材时先确认授权和脱敏方案再动手。这条没有例外。关于后续方向跑完这七天你会有几个自然的延伸口想做效果更好可以去研究偏好对齐和评测体系想做工程化可以去研究推理加速、量化部署和并发调度想做应用可以把 RAG 和 Agent 组合成带记忆的多轮助手。每一条都能深挖很久但前提是你已经把这条最小链路亲手跑通过一遍。建议把这七天的代码整理成一个独立仓库加一个能一键跑通的 README这东西以后会反复派上用场。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑