这两年问我"AI大模型怎么入门"的人,比问我"电脑怎么重装系统"的还多。问的人里头,有做后端开发的、有做产品的、有做运营的,甚至还有做机械设计的。大家焦虑点都差不多:刷到的东西全是"千亿参数""对齐""推理加速"这类词,看着热闹,但自己电脑上连个模型都跑不起来。所以这篇AI大模型入门教程,我不打算从论文讲起,而是按一个零基础的人真实的动手顺序来铺:先把环境支起来,把第一个模型跑通,再回头补那几个必须懂的概念,最后往前走一步,做提示工程、检索增强和微调。看完你会知道这东西是什么、能干什么、需要多少硬件、坑在哪,以及哪些部分可以先跳过不学。适合完全没接触过的朋友照着抄,也适合已经会调用接口、但一直没搞明白底层在干什么的人补课。
1. 入门之前,先分清你要走哪条路
我见过太多人入门失败,不是因为笨,是因为一开始就走错了层。有人上来就要复现预训练,卡在数据清洗三个月;有人只想给自己的小工具加个问答功能,却跑去啃注意力机制的推导。大模型这个领域,上下差了不止一个数量级,先定位清楚自己在哪一层,比学什么更重要。
1.1 三个层次:应用、微调、训练
我把大模型相关的活儿粗分成三层,你可以对号入座。
应用层,就是调用别人训练好的模型,通过接口或者本地部署,把它接进自己的产品或工作流里。这一层不需要显卡,不需要懂梯度下降,主要工作是提示词设计、上下文拼接、结果校验、成本控制。绝大多数人应该从这里入门,因为它反馈快、投入小,一天就能出成果。
微调层,是你手上有一个通用的基座模型,但它在你的垂直场景里表现不够好,比如不懂你们行业的术语、回答格式老是不对、语气不像客服。这时候你用几百到几万条自己的数据,在基座模型上再训练一小会儿,让它更贴合你的任务。这一层需要一张有足够显存的显卡,需要会处理数据,但不需要从头训练。
训练层,就是从随机初始化的参数开始,用海量语料把模型喂出来。这一层是机构级玩法,几百张卡起步,个人基本碰不到。你了解它的原理有助于理解模型行为,但不必真的动手。
提示:新手最容易犯的错,是把"微调"当成万能药。很多效果问题,改提示词、加检索、换更大的模型就能解决,根本轮不到微调。顺序应该是:先调提示词,再上检索,最后才考虑微调。
1.2 大模型正在影响哪些具体场景
搞清楚方向之后,再看这东西到底改变了什么。我按自己接触过的项目归类一下,你会发现影响范围比想象中宽,但也没那么玄。
第一类是文本处理类,包括摘要、改写、翻译、分类、信息抽取、格式转换。这类任务的特点是输入输出都是文本,评价标准相对客观,落地最快。很多公司内部的第一批大模型应用,就是拿它做合同要素抽取、工单自动分类、会议纪要整理。
第二类是知识问答类,典型形态是企业内部的知识库助手。这类任务单靠模型本身不行,必须配合检索,因为模型不知道你公司的内部文档。技术上就是"检索增强生成",后面第 5 章会详细拆。
第三类是代码与结构化生成,包括代码补全、SQL 生成、配置生成、接口文档生成。这类任务对模型能力要求高,但收益也直接,开发者自己就是用户,反馈闭环很短。
第四类是多模态类,图像理解、语音转写、文档版面解析。这类通常要专门的模型,入门阶段先放一放,等你把纯文本这条线走顺了再说。
影响范围上,我个人的观察是:重复性高、有明确输入输出格式、容错率尚可的环节,被替代或改造的速度最快;需要承担责任、需要现场判断、需要跨部门协调的环节,短期内还是人主导,模型当助手。这个判断对规划学习方向很有用——你学的技能最好落在"人机协作"那一侧,而不是"纯执行"那一侧。
1.3 我建议的倒序学习法
常规教程都是从原理讲到应用,我建议反过来:先跑通,再理解,最后优化。
具体做法是,第一天不看任何理论,直接找一个能在你机器上跑起来的小参数模型,把它跑出第一句话。这个过程会让你立刻撞上几个真实问题:显卡够不够、模型文件从哪来、依赖版本对不对、显存爆了怎么办。带着这些具体问题回去看原理,效率比干读高十倍。
等你能稳定跑通推理了,再去理解分词器、上下文窗口、采样参数这三样东西,因为它们直接决定你调参时的直觉。再往后才是微调、量化、部署这些进阶内容。
这个顺序的好处是每一步都有可验证的成果,不会出现"学了两周还不知道自己在学什么"的挫败感。坏处是知识结构一开始是零散的,需要你在第 3 章那部分主动补一遍框架。我试过带人用这个路径,比正序快大概一倍。
2. 学习路线与资源投入的账怎么算
动手之前先把账算清楚,能省掉大量中途放弃。硬件、时间、软件环境这三样,是新手最先卡住的地方,而且卡住的方式高度相似。
2.1 时间与算力预算表
下面这张表是我按常见情况估的,你可以对照自己的目标看需要投入多少。注意这里的时间指的是"有效学习时间",不是刷视频的时间。
| 目标层级 | 典型产出 | 硬件门槛 | 预估投入 | 主要难点 |
|---|---|---|---|---|
| 应用体验 | 本地跑通对话、能改提示词 | 8GB 显存或纯 CPU 也能凑合 | 3 到 5 天 | 环境配置、模型文件获取 |
| 应用开发 | 接进自己的工具,处理批量任务 | 12GB 显存较舒适 | 2 到 3 周 | 上下文管理、结果校验、成本 |
| 检索增强 | 私有文档问答系统 | 12 到 24GB 显存 | 3 到 6 周 | 文档切分、召回质量、评测 |
| 参数高效微调 | 垂直场景专用模型 | 24GB 显存起步 | 1 到 2 个月 | 数据质量、显存调优、过拟合 |
| 全参微调 | 领域适配的完整模型 | 多卡,百 GB 级显存 | 3 个月以上 | 分布式、稳定性、成本 |
这张表里最容易被低估的是"数据质量"这一项。很多人以为微调的门槛在显卡,实际上真正让人卡住的是拿不出几百条干净、格式统一、标注一致的训练样本。显卡可以租,数据只能自己一条条整理。
2.2 硬件与显存的真实门槛
显存这件事,得给你一个能自己算的公式,而不是记结论。
模型加载进显存,权重占的大头,算法是:参数量 × 每个参数占用的字节数。半精度(16 位)是 2 字节,8 位量化是 1 字节,4 位量化是 0.5 字节。所以一个 70 亿参数的模型:
- 半精度加载:7 × 10⁹ × 2 字节 ≈ 14GB
- 8 位量化:≈ 7GB
- 4 位量化:≈ 3.5GB
但显存不是只放权重。还要放激活值和KV 缓存。KV 缓存跟上下文长度成正比,算法大致是:2 × 层数 × 注意力头数 × 每头维度 × 序列长度 × 批大小 × 字节数。举个具体例子,一个 32 层、32 个注意力头、每头维度 128 的模型,半精度下每个 token 的 KV 缓存是:
2 × 32 × 32 × 128 × 2 字节 = 524288 字节 ≈ 0.5MB
听起来很小,但乘上 8192 的上下文长度就是 4GB 左右,再乘批大小 4 就是 16GB。这就是为什么很多人发现"模型明明只有 14GB,24GB 的卡却说显存不足"——吃在缓存上了。
实操建议是这样:如果你的显卡显存是 8GB,选 70 亿参数模型加 4 位量化,上下文控制在 2048 以内,单条推理没问题;如果是 12GB,可以上 4 位量化加 4096 上下文;24GB 就比较宽裕了,70 亿参数半精度也能跑,或者上更大参数的量化版本。没有独显也能玩,纯 CPU 跑 4 位量化的小模型,速度大概是每秒几个字,用来理解原理够了。
注意:显存不足时,系统和驱动会把部分数据挪到内存里,表现是程序不报错但速度断崖式下跌。看到"能跑但慢得离谱",先怀疑显存,别怀疑代码。
2.3 软件环境的最小可用组合
软件这块,我建议只装四样东西:一个包管理隔离环境、一个深度学习框架、一个模型加载与分词工具库、一个训练加速库。其他的等用到再装。
关键原则是版本对齐。深度学习生态里版本冲突是最高频的报错来源,显卡驱动版本、并行计算运行时版本、框架版本、加速库版本,这四个必须互相兼容。我的习惯是先在官方文档里确认框架支持的运行时版本区间,再倒推去装驱动,而不是先把驱动升到最新。
隔离环境这件事别省。我见过太多人把系统环境搞乱,最后只能重装。用虚拟环境把每个项目隔开,出问题直接删掉重建,成本极低。
# 示意:创建隔离环境并安装最小依赖(包名以官方文档为准) 创建环境 --name llm-demo --python 3.10 激活环境 llm-demo 安装 深度学习框架 # 注意选择与你的驱动匹配的版本 安装 模型加载与分词库 安装 训练加速库 安装 量化推理支持库装完之后做一次自检,确认框架能识别到显卡:
# 示意代码 import 框架 print(框架.版本号) print(框架.是否可用显卡()) # 期望输出 True print(框架.显卡数量()) print(框架.显卡名称(0))如果这里输出的是 False,别往下走,先解决驱动问题,否则后面所有报错都会指向错误的方向。
3. 几个绕不过去的核心概念拆解
能跑通之后,就该补概念了。这一章我只挑四个真正影响你日常操作的:分词器、注意力与上下文、显存换算、采样参数。其余的原理,等你需要改模型结构时再补。
3.1 分词器:模型眼中的文字
模型不认识汉字,也不认识英文单词,它只认识数字。分词器干的事,就是把你的文本切成一个个"词元",再映射成编号。
这里有个反直觉的点:词元的切法和你想的不一样。中文里一个常见词可能是一个词元,一个生僻字可能被切成两三个词元,一串数字可能被切成单个数字。这带来两个实际影响。
第一,计费和长度限制按词元算,不按字数算。同样是一千个汉字,用词不同,词元数可能差 30%。做成本估算时,中文一般按"一个字约等于 1 到 1.5 个词元"来估,英文按"一个单词约等于 1.3 个词元"估。
第二,模型对生僻词和数字的处理能力弱。因为切得太碎,模型很难学到完整语义。这就是为什么模型算数学题容易错、念身份证号容易漏位。工程上的解法是把数字和编号用特殊符号隔开,或者干脆不让模型算,让它输出表达式再由程序计算。
# 示意:观察同一句话的不同切法 文本 = "入门教程2024版" 词元列表 = 分词器.编码(文本) print(词元列表) # 一串数字编号 print(分词器.解码(词元列表)) # 还原成文本 print(len(词元列表)) # 这个词元数就是你要付费/占上下文的量养成习惯:写提示词之前先看一眼词元数,你会发现很多"模型不听话"的问题,其实是提示词太长被截断了。
3.2 注意力与上下文窗口
注意力机制说白了就是:模型在生成每一个字的时候,回头看一眼前面所有的字,判断哪些跟当前这个字关系大,给它们分配不同的权重。这个"回头看"的范围,就是上下文窗口。
上下文窗口决定了三件事:能塞多少资料、能聊多久不健忘、显存吃多少。早期的模型窗口只有 2048 个词元,现在动辄几万甚至几十万。但窗口大不等于效果好,这里有个业内公认的现象:信息放在长上下文的中间位置,模型容易忽略,放在开头和结尾反而记得牢。所以做长文档问答时,重要的内容尽量往前提。
还有个实操细节:多轮对话是把历史全部拼进去的,聊得越久,每次请求带的词元越多,成本和延迟都在涨。工程上的做法是做历史压缩——保留最近几轮原文,更早的做摘要,或者只保留与当前问题相关的片段。
3.3 参数量、精度、显存三者的换算
这个概念在第 2 章已经给了公式,这里补充训练侧的算法,因为它直接决定你能不能微调。
推理只需要放权重和缓存,训练还要放梯度和优化器状态。用常见的自适应优化器,每个参数要额外存两份状态,加上梯度一份,半精度训练下大约是每个参数 16 到 20 字节。也就是说,70 亿参数的模型做全参微调,光静态占用就要 110GB 以上,还没算激活值。这就是为什么个人玩家基本只能做参数高效微调——它只训练很小一部分额外参数,主干冻结,显存需求能压到全参的几分之一。
| 训练方式 | 每参数字节数(估算) | 70 亿参数所需显存 | 个人可行性 |
|---|---|---|---|
| 全参微调(半精度) | 16 到 20 | 110GB 以上 | 需要多卡 |
| 全参微调(混合精度+卸载) | 10 到 14 | 70 到 100GB | 勉强,速度慢 |
| 参数高效微调(半精度) | 2 到 4 | 15 到 30GB | 24GB 卡可行 |
| 参数高效微调(4 位量化) | 1 到 2 | 8 到 15GB | 12GB 卡可行 |
这张表是我实际测下来比较接近的估计,具体数字会因批次大小、序列长度、是否开启梯度检查点而浮动,但量级是对的。
3.4 采样参数决定输出性格
同一个模型,参数不同,出来的东西像两个模型。这四个参数你必须搞明白。
温度控制随机性。取值越低越保守、越确定,适合抽取、分类、代码;取值越高越发散,适合创意写作。我一般用 0.7 做通用对话,0.2 到 0.3 做结构化抽取,1.0 以上只在需要多样性时用。
top_p(核采样)控制候选范围。设为 0.9 表示只从累计概率前 90% 的词里挑。它和温度有联动,一般不同时调,调一个就行。
top_k是硬性限制候选数量,取 40 左右比较常见。多数场景下 top_p 已经够用,top_k 可以留默认。
重复惩罚用来压制复读机现象,1.05 到 1.1 是比较安全的区间,调太高会导致用词僵硬、语句不自然。
提示:调试时先把温度设成 0,排除随机性干扰,确认逻辑对不对;逻辑对了再往上调温度做体验优化。反过来做,你会分不清是模型能力问题还是随机性问题。
4. 实操:从零把模型跑起来
前面都是铺垫,这一章是真正动手的部分。我按自己的习惯顺序写,你照着走一遍,中间遇到报错就翻第 7 章的速查表。
4.1 模型文件与目录规划
先说文件。你从社区拿到的模型,常见有两种形态:一种是分片的权重文件加一个配置文件,需要加载库来读;另一种是已经打包成单文件的量化格式,配合专门的推理程序用。前者灵活、方便微调,后者部署简单、资源占用低。
目录我建议这样规划,避免以后文件到处飞:
项目根目录/ ├── models/ # 所有模型,一个子目录一个模型 │ ├── base-7b/ │ │ ├── 配置文件 │ │ ├── 分词器文件 │ │ └── 权重分片 │ └── base-7b-int4/ # 量化版本单独放 ├── data/ # 训练与评测数据 ├── outputs/ # 微调产物、日志 ├── scripts/ # 脚本 └── env/ # 隔离环境模型文件动辄几个 G 到几十个 G,下载前先确认磁盘空间,并且一定要校验完整性。很多"加载失败"的报错,根子是下载中断导致文件损坏。正规的分发一般会提供校验值,下完对一遍,能省掉几小时的排查。
还有一点:模型文件路径里不要有中文和空格。这个问题在 Windows 上尤其常见,报错信息往往指向别处,很难查。
4.2 第一次推理的完整流程
流程一共五步:加载分词器、加载模型、编码输入、生成、解码输出。
# 示意代码,接口名按你所用框架替换 from 加载库 import 自动分词器, 自动因果模型 模型路径 = "./models/base-7b-int4" 分词器 = 自动分词器.从目录加载(模型路径, 信任远程代码=False) 模型 = 自动因果模型.从目录加载( 模型路径, 设备映射="自动", # 自动分配到显卡/内存 量化方式="4位", ) 模型.设为评估模式() 提示词 = "用三句话解释什么是分词器:" 输入 = 分词器.编码(提示词, 返回张量=True).到(模型.设备) 输出 = 模型.生成( 输入, 最大新词元数=256, 温度=0.7, top_p=0.9, 重复惩罚=1.1, 遇结束符停止=True, ) print(分词器.解码(输出[0], 跳过特殊词元=True))几个容易出错的地方。信任远程代码这个参数,除非你确认模型来源可靠,否则保持关闭,它是安全边界。设为评估模式会关掉随机失活,忘了写会导致输出不稳定。最大新词元数要设,不设的话模型可能一直生成到显存耗尽。
第一次跑建议先用最短的提示词、最小的输出长度,确认整条链路通,再逐步加长。我见过有人一上来就丢一篇长文档进去,然后被 20 分钟的等待时间劝退。
4.3 显存不够时的四种降级手段
按代价从低到高排,我一般这么试:
第一种,降精度。半精度换 8 位或 4 位量化,显存直接砍一半到四分之三,效果损失在多数任务上不明显。这是性价比最高的一招。
第二种,缩上下文。把最大输入长度从 8192 降到 2048,KV 缓存能省下大半。做法是在编码时截断,或者先做一轮摘要再喂进去。
第三种,减批次。批量推理能提高吞吐,但显存是线性涨的。调试阶段批大小设成 1,稳定了再往上加。
第四种,CPU 卸载。把部分层放到内存里,显存不够也能跑,代价是速度慢几倍。这招适合验证效果,不适合做服务。
注意:四种手段可以叠加,但别一次全上。一次只改一个变量,否则出问题你不知道是哪一项导致的。
4.4 流式输出与服务化封装
真正给人用的东西,必须有流式输出。等模型把 500 个字全生成完再一次性显示,体感是差了十倍的。流式的实现思路是:每生成一个词元就吐出来,前端边收边渲染。
# 示意:流式生成 for 片段 in 模型.流式生成(输入, 最大新词元数=512): 新增文本 = 分词器.解码(片段, 跳过特殊词元=True) print(新增文本, end="", flush=True)封装成服务时,有三件事必须提前想好:并发上限、超时策略、上下文长度限制。大模型推理是重资源操作,不设并发上限,几个请求就能把显存打爆;不设超时,一个卡住的请求会拖垮整个队列;不限制上下文,用户贴一篇长文进来你就得吃满缓存。
我的做法是加一个简单的排队机制,同时进来的请求按顺序处理,超过等待阈值就直接返回"当前繁忙"。体验上比让用户干等三分钟强。
5. 用得好的关键:提示工程与检索增强
到这一步你已经能跑通模型了,但大概率会觉得"好像也就那样"。差距主要在提示词和上下文管理上,这一章讲怎么把效果拉起来。
5.1 提示词的结构化模板
我调试出来的经验是:把提示词当成一份需求文档来写,而不是一句话许愿。一个稳定的模板包含六块。
角色:告诉它站在什么位置回答。比如"你是一名负责售后政策解答的客服专员"。角色不需要玄乎,具体比华丽有用。
任务:一句话说清楚要做什么,用动词开头。"从下面的文本中抽取合同金额、签约日期、双方名称"。
输入:用明确的分隔符把待处理内容和指令隔开,比如三引号或者固定标记。这一步能显著降低"模型把指令当成内容"的概率。
约束:列清楚不许干什么。"不要编造原文中没有的信息""无法确定时输出'未提及'"。
输出格式:给出精确的格式要求,最好带一个例子。要 JSON 就把字段名、类型、嵌套结构全写清楚。
示例:一到三个高质量示例,效果通常超过大段文字描述。这就是常说的少样本提示。
# 角色 你是一名技术文档编辑。 # 任务 把下面的原始说明改写成分步骤的操作指引。 # 约束 - 不增加原文没有的步骤 - 每步不超过 40 字 - 遇到含糊处标注【待确认】 # 输出格式 1. 步骤内容 2. 步骤内容 # 输入 """ (这里放原始说明) """这套模板我用了很久,稳定性明显高于那种"请你帮我……"的一句话提示。另外提醒一句:提示词不是越长越好。堆到几千字,模型反而会抓不住重点,而且每次请求的成本都在涨。控制在 500 字以内,把关键约束说清楚就够了。
5.2 检索增强的完整链路
模型不知道你的私有资料,硬要它答就会编。检索增强的思路很朴素:先从你的资料库里找出相关片段,再把这些片段和问题一起交给模型,让它基于给定材料回答。
链路分五步。
第一步,文档解析。把各种格式的文档转成纯文本,保留标题层级。解析质量决定上限,扫描件没做文字识别,后面全是白搭。
第二步,切块。把长文档切成小块,每块 300 到 800 字,块之间留 10% 到 20% 的重叠,避免把一句话切断。块太大检索不准,块太小语义不完整。标题可以拼到每个块的开头,能明显提升召回质量。
第三步,向量化与入库。用嵌入模型把每个块转成向量,存进向量库。这一步要注意:问题和文档必须用同一个嵌入模型,混用会导致检索完全失效。
第四步,召回与重排。用户提问时先向量化,找最相近的前 10 到 20 个块,再用一个重排模型精排出前 3 到 5 个。只做向量召回不做重排,是很多问答系统答非所问的根因。
第五步,拼接与生成。把选中的块按相关度排序拼进提示词,明确要求"只依据以下材料回答,材料中没有的信息回答不知道"。
这套链路里,我踩过最深的坑是只测生成不测召回。检索没找到正确材料,你再怎么调提示词也没用。正确的做法是先把召回单独评测:准备一批问题,人工标注每个问题的答案在哪个块里,然后看检索的命中率。命中率低于 80%,先优化切块和嵌入模型,别急着改提示词。
5.3 结构化输出与结果校验
但凡要把模型输出接进程序,就必须做结构化。做法是要求模型输出 JSON,并且给出严格的字段定义。
但模型不保证一定守规矩——多一个逗号、少一个引号、字段名换个写法,程序就崩了。所以要有兜底:解析失败就重试,重试时把错误信息一起塞回去,让它自己修。一般重试两次能解决 90% 的格式问题。还不行就降级,返回一个默认结构,别让整个流程挂掉。
# 示意:带重试的结构化抽取 def 抽取(文本, 最大重试=3): for 次数 in range(最大重试): 结果 = 调用模型(构造提示词(文本)) try: return 解析为字典(结果) except 解析异常 as 错误: 文本 = 文本 + f"\n上次输出格式错误:{错误},请只输出合法 JSON。" return 空结果模板() # 兜底,不抛异常除了格式校验,内容校验也得有。比如抽取出来的金额要做范围检查,日期要做合法性检查,人名要做长度检查。模型会一本正经地编,程序得有一道闸门。
6. 微调:什么时候值得动手
到这一章,说明你已经把前面的都跑顺了。微调是很多人的执念,但我得先泼盆冷水:大部分场景不需要微调。
6.1 全参微调与参数高效微调的取舍
判断标准很简单:如果你的问题是"模型不知道某类知识",去做检索增强;如果是"模型不懂某种输出格式或说话方式",才考虑微调。
微调方式上,个人和小团队只看一种就够了:参数高效微调。原理是在原来的权重旁边挂一对小矩阵,只训练这对小矩阵,主干完全冻结。因为小矩阵的秩很低,参数量可能只有原模型的千分之一,显存和训练时间都降下来了。训练完可以把小矩阵合并回主干,推理时不增加额外开销。
几个关键参数的经验值:
| 参数 | 含义 | 常用取值 | 说明 |
|---|---|---|---|
| 秩 | 小矩阵的维度 | 8 到 64 | 任务越复杂取值越大,格式类任务 8 就够 |
| 缩放系数 | 控制新增权重的强度 | 通常取秩的 2 倍 | 与秩联动,别单独调 |
| 学习率 | 每步更新幅度 | 1e-4 到 3e-4 | 比全参微调高一个量级 |
| 作用模块 | 挂在哪些层上 | 注意力层的四个投影 | 效果不够再扩展到全线性层 |
| 训练轮数 | 数据过几遍 | 2 到 3 | 超过 3 轮基本都在过拟合 |
数据量方面,几百到几千条高质量样本就能看到明显效果。我做过的最小的一个格式对齐任务,用了 400 条样本、单卡跑了 40 分钟,输出格式的合规率从 62% 提到 96%。
6.2 数据准备的真实工作量
这部分没人愿意讲,但它占了整个微调流程 70% 的时间。
数据格式一般是"指令 + 输入 + 期望输出"的三元组。真正的功夫在清洗上:去重(重复样本会让模型死记硬背)、去噪(错别字、乱码、多余空格)、统一格式(标点、大小写、字段顺序)、控制长度分布(别全是短样本,也别全是长样本)。
还有一个容易被忽略的点:损失掩码。训练时你希望模型学的是"怎么回答",不是"怎么复述问题"。所以计算损失时要屏蔽掉指令部分,只对回答部分计算。很多新手做完微调发现模型只会重复提问,就是这里没设对。
数据质量的判断标准,我给三条:同一类问题至少 50 条覆盖、答案风格统一、答案里不包含模型无法验证的臆造内容。做不到这三条,宁可先不微调。
6.3 一次完整的参数高效微调流程
流程是:加载基座、挂载微调模块、准备数据、配置训练参数、训练、保存、合并、评测。
# 示意代码 基座 = 自动因果模型.从目录加载("./models/base-7b", 量化方式="4位") 分词器 = 自动分词器.从目录加载("./models/base-7b") # 挂载微调模块 配置 = 微调配置( 秩=16, 缩放系数=32, 作用模块=["查询投影", "键投影", "值投影", "输出投影"], 丢弃率=0.05, ) 模型 = 注入微调模块(基座, 配置) # 训练参数 训练参数 = 训练配置( 每卡批大小=1, 梯度累积步数=8, # 等效批大小 8 学习率=2e-4, 训练轮数=3, 最大序列长度=1024, 梯度检查点=True, # 用时间换显存 混合精度="半精度", ) 训练器 = 训练器类(模型=模型, 训练集=训练数据, 参数=训练参数) 训练器.训练() 训练器.保存("./outputs/adapter-v1") # 合并进主干,方便部署 合并后模型 = 合并微调模块(模型) 合并后模型.保存("./outputs/merged-v1")这里几个参数值得说明。梯度累积是在显存受限时模拟大批次的手段,8 步累积等于批大小 8,效果接近但速度略慢。梯度检查点是牺牲 20% 到 30% 的速度换取大幅显存节省,显存紧张时必开。最大序列长度要按你的数据实际长度分布来定,砍太短会截断答案。
6.4 训练指标与效果验收
训练日志里有两个数字要盯:训练损失和验证损失。
训练损失持续下降是正常的。验证损失下降然后开始上升,说明过拟合了,立刻停。验证损失一开始就不降,可能是学习率太大或者数据格式有问题。
但损失只是参考,真正的验收要看任务指标。做法是准备一份独立的测试集,不要和训练集混在一起,然后跑三个对比:基座模型、微调后模型、以及一个人工写的参考答案。对比维度包括格式合规率、内容准确率、风格一致性。
我一般的验收标准是:格式合规率要到 95% 以上,内容准确率不能低于基座模型(有时候微调会把通用能力练坏,这叫灾难性遗忘),风格一致性靠人工抽检 50 条。
提示:微调前后一定要做同一批测试,别凭感觉说"好像变好了"。我见过有人微调完觉得效果好,一测发现通用问答能力掉了两成。
7. 常见问题与排查实录
这一章全是我自己踩过和别人问过的问题,按类型整理成速查表,遇到时对着找。
7.1 显存与运行类问题速查
| 现象 | 最可能原因 | 处理办法 |
|---|---|---|
| 启动就报显存不足 | 模型精度太高或上下文太长 | 换 4 位量化,上下文降到 2048 |
| 生成到一半崩掉 | KV 缓存随长度增长撑爆 | 限制最大新词元数,开缓存回收 |
| 能跑但每秒不到一个字 | 数据被换到内存里了 | 降精度、减批次、缩短上下文 |
| 加载报文件损坏 | 下载不完整 | 校验文件完整性,重新下载 |
| 加载报路径错误 | 路径含中文或空格 | 换成纯英文短路径 |
| 输出重复同一句 | 重复惩罚太低或温度太低 | 惩罚提到 1.1,温度提到 0.7 |
| 输出乱码或特殊符号 | 解码时没跳过特殊词元 | 解码加跳过特殊词元参数 |
| 首次调用特别慢 | 模型在预热、编译 | 预热一次空请求,再做健康检查 |
其中"能跑但极慢"这个现象最坑人,因为不报错。判断方法是看显卡占用:显存占满但利用率很低,基本就是数据在内存和显存之间来回搬。解决办法就是降精度或者缩上下文,没有别的捷径。
7.2 效果类问题速查
| 现象 | 常见根因 | 排查顺序 |
|---|---|---|
| 答非所问 | 检索没召回正确材料 | 先单独测召回命中率 |
| 一本正经编造 | 提示词没约束"不知道就说不知道" | 加约束,加"仅依据材料"要求 |
| 不遵守输出格式 | 格式描述模糊或缺示例 | 给完整字段定义加一个示例 |
| 长文档后半段被忽略 | 关键信息在上下文中部 | 重要内容前置,或先摘要再问 |
| 多轮对话越聊越乱 | 历史太长,噪声累积 | 做历史压缩,只留相关轮次 |
| 中文回答夹英文 | 训练语料分布导致 | 提示词明确要求全中文输出 |
| 数字计算老出错 | 分词把数字切碎了 | 让模型输出表达式,程序计算 |
| 微调后通用能力下降 | 过拟合或数据太单一 | 减轮数,混合通用数据一起训 |
这里我特别想说"编造"这条。模型编造不是 bug,是它的工作机制——它本质是在预测下一个最可能的词,不知道的时候也会给出一个"看起来合理"的答案。所以工程上必须有校验层,不能把模型的输出直接当事实用。
7.3 部署与并发类问题
上线之后问题会变个样子。最常见的三个:并发打爆显存、长请求拖垮队列、内存泄漏导致越来越慢。
第一个靠限流解决,同时处理的请求数不要超过显存的承受能力,超出的排队。第二个靠超时,给每个请求设上限,超时直接中断并返回提示。第三个要靠定期重启或者主动释放缓存,长期运行的服务,每天定时重启一次是很朴素但有效的办法。
还有个实践细节:模型加载只做一次。我见过有人在每个请求里都重新加载模型,响应时间直接飙到分钟级。模型应该在服务启动时加载成全局单例,请求进来只做推理。
8. 我在实操中踩过的几个坑
最后分享几个具体教训,都是文档里不会写的。
第一个坑,追新。刚入门那阵子,我每隔几天就换一个新出的模型试,结果每个都只跑了个 demo,一个都没吃透。后来我强迫自己用同一个模型做了三个完整项目,理解才真正建立起来。模型会一直更新,方法论的沉淀才是你的。
第二个坑,跳过评测。我做过一个文档问答,自己试了十几个问题感觉都对,交付后用户问的第一个问题就答错了。后来补了一套 100 条问题的测试集,才发现召回率只有 60%。没有测试集的优化都是瞎调,这句话我現在写在每个项目的第一页。
第三个坑,把提示词当代码写。提示词改动带来的效果波动,比代码改动大得多。所以提示词也要版本管理,每次改动记录改了什么、为什么改、效果怎么变。我现在每个项目都有一个提示词变更记录表,看起来麻烦,省下来的返工时间远超这点投入。
第四个坑,低估数据清洗。第一次做微调,我拿了两千条爬来的问答直接开训,训完模型学会了一堆奇怪的语气词和错别字。后来老老实实花了三天做清洗,样本从两千条降到七百条,效果反而好了一大截。数据这件事,少而精永远胜过多而杂。
第五个坑,忽视成本。早期做应用没算过账,上线一个月才发现调用费用比预期高五倍。后来做了三件事把成本压下来:简单任务用小模型、加结果缓存、把能本地跑的请求本地跑。做技术方案时把成本当成一等技术指标,这个习惯越早养成越好。
如果你现在刚开始,我建议今天就做一件事:不管用什么办法,让你的机器上跑出第一句话来。哪怕是用最笨的方式、最小的模型、最慢的速度。跑通那一下的体感,比看十篇文章都管用。跑通之后再回头看第 3 章那几个概念,你会发现每个字都变得具体了。