一、训练:把随机数字变成有用的数字
先分清两件常被混为一谈的事。
训练是造模型,推理是用模型。训练拿海量文本反复做文字接龙——预测下一个 token,猜错了就把内部参数往「让正确答案概率更高」的方向调一点。这个循环要转很多很多遍。推理则是拿这堆已经固定下来的数字,对你的输入算一遍,得到下一个 token;每调用一次 API,就是一次推理。
分界线只有一条:参数动不动。
训练时参数一直在被修改,推理时参数是只读的。
这条推论的用处比看上去大:模型不会因为跟你聊过什么而改变。对话历史是每次请求重新打包发给它的,不是它记住了。想让它变,只能回去改文件里的数字——那又是训练。
训练留下了什么
产物是一大堆固定下来的数字:词表、embedding 矩阵、每一层的权重,打包成你能下载到的模型文件。以 Qwen3-0.6B 为例,model.safetensors里是 311 块张量:
model.embed_tokens.weight [151936, 1024] model.layers.0.self_attn.q_proj.weight [2048, 1024] model.layers.0.mlp.gate_proj.weight [3072, 1024] ... model.layers.27.mlp.down_proj.weight [1024, 3072]模型「学到的东西」就是这样一堆二维矩阵,没有一个是人写的。
embedding 那张表最能说明训练在干什么。刚建表时里面全是随机数;训练过程中,经常出现在相似位置的词——比如「我养了一只 ___」里的「猫」和「狗」——它们的向量会被推到相近的位置。所谓「语义相近的向量也相近」,不是设计出来的,是海量接龙的副产品。151,936 × 1,024 ≈ 1.56 亿个数,每个数 2 byte(BF16),光这一张表就占 311MB。
训练存档:checkpoint
训练要持续调整大量参数,中途断了不能从头再来,所以训练程序会定期把当时的状态存下来:当前权重、优化器状态、已完成的步数。这份存档叫 checkpoint,仓库里的checkpoint-1000、checkpoint-2000就是不同步数的版本。
普通使用者应该挑作者明确发布的最终版本。中间存档可能还没训到位,效果不理想。
训练之前,先切数据
这块是我笔记里最完整、也最容易被跳过的一段。
模型学规律用训练集,调参和选模型用验证集,最终评估用测试集。三件事由三批互斥的数据来做。
| 集合 | 干什么 | 关键约束 |
|---|---|---|
| 训练集 | 更新模型参数 | 也是预处理唯一能拟合的地方 |
| 验证集 | 调参、早停、选模型 | 不更新参数,但反复用很多轮也会被用脏 |
| 测试集 | 最终评估 | 训练和调参阶段必须隔离,不能拿来调参 |
三条最容易踩的线:
- 不能有样本重叠。哪怕只有一条样本同时出现在两个集合里,评估隔离就已经破了。
- 预处理只在训练集上拟合。标准化、归一化、特征选择、PCA——这些步骤如果提前看见了全量数据,就是预处理泄漏,分数会虚高。
- 时间序列不能随机打乱。按时间顺序切,否则未来信息会漏到过去。
数据量不足以切固定验证集时,可以先留出测试集,再在训练集内部做 K 折交叉验证:
fromsklearn.model_selectionimporttrain_test_split,KFold# 测试集先独立留出,之后不再碰X_train_all,X_test,y_train_all,y_test=train_test_split(X,y,test_size=0.2,random_state=42,stratify=y)# 剩下的在训练集内部轮流当训练/验证kf=KFold(n_splits=5,shuffle=True,random_state=42)fortr_idx,va_idxinkf.split(X_train_all):X_tr,X_va=X_train_all[tr_idx],X_train_all[va_idx]# 每一折都要重新拟合预处理器,再 transform 验证部分要注意的是,交叉验证给出的是「某套超参数或方法」的平均泛化能力,不是某一个最终模型的分数——每一折训练出来的都是独立模型。
二、微调:从只会接话到会答话
这一节短,因为笔记里就这么多。
Base 模型是纯粹的文本补全模型,只会「接话」。你把问题丢给它,它可能顺着续写更多问题——因为它学的是「下一段文字长什么样」,不是「怎么回答问题」。
Instruct 模型是在 Base 基础上做过指令对齐的,会把你的输入当成问题来回答。Chat 模型同类。
于是选择很直接:
- 日常聊天、写代码、当助手 → 选
Instruct或Chat版本 - 想继续预训练、或者自己微调 → 才需要 Base 版本作起点
模型卡上通常会标清楚这是 Base、Instruct 还是 Chat。另外,微调后的权重通常仍存成 Safetensors,和原始权重是同一套文件格式,只是里面的数字变了。
还有一句值得单独拎出来:同规模的不同模型、同模型的不同微调版本,实际表现差距可能很大。别看到 7B 就当它们是同一个水平。
三、量化与精度:每个数占几个字节
字母 B 在模型语境里有两个毫不相干的含义。
| 出现在哪 | B 的全称 | 含义 | 例子 |
|---|---|---|---|
GB/MB/KB/TB | Byte(字节) | 存储大小 | 权重文件 1.50GB;显存 28GB |
14B/0.6B/70B | Billion(十亿) | 参数量 | 14B ≈ 140 亿个参数 |
最典型的误读是把qwen3:0.6b里的0.6b当成 0.6GB。这个模型的实际磁盘占用是 522MB——看着「碰巧接近」,但两者毫无关系:参数量 6 亿是「有多少个数字」,522MB 是「这些数字存下来占多大」。
一句话记法:
看到 G/M/K 打头的容量单位,B 是字节,问的是「占多大」;
看到光秃秃的 N B 当模型名,B 是十亿,问的是「多少个参数」。
一条估算公式
占用 ≈ 参数量 × 每个参数占用的字节数
参数量定「有多少个数」,数值格式定「每个数占几个字节」,两者相乘才是文件大小 / 显存占用。字节数的算法很直白:位数 ÷ 8,FP32 就是 32 ÷ 8 = 4。
| 精度 | 每个参数占用 |
|---|---|
| FP32 | 4 字节 |
| FP16 / BF16 | 2 字节 |
| INT8 | 1 字节 |
| INT4 | 0.5 字节 |
| 2-bit | 0.25 字节 |
代入 14B(约 140 亿参数):
| 精度 | 估算占用 |
|---|---|
| FP32 | 约 56 GB |
| FP16 | 约 28 GB |
| INT8 | 约 14 GB |
| INT4 | 约 7 GB |
| 2-bit | 约 3.5 GB |
这个式公式是我平时计算「我这台机器跑不跑得动」用的。
FP 和 INT 是两个家族
| 格式族 | 全称 | 特点 | 常见档位 |
|---|---|---|---|
| FP | Floating Point(浮点数) | 能表示小数,数值范围广 | FP32(4 字节)、FP16 / BF16(2 字节) |
| INT | Integer(整数) | 更省空间,计算更快 | INT8(1 字节)、INT4(0.5 字节) |
容易混的一点:FP / INT 说的是「哪一族」,FP16 / INT4 这类说的是「哪一族 + 多少位」。
- 同一族内部的比较(FP32 → FP16 → BF16)是精度问题
- 跨族的转换(FP16 → INT8 → INT4)通常就是量化
训练几乎都用 FP 家族,因为梯度更新需要足够的动态范围;INT 家族省空间、算得快,代价是能表示的值的粒度变粗。
精度是名词,量化是动词
| 精度 | 量化 | |
|---|---|---|
| 回答的问题 | 用多少位表示一个数? | 怎么从高精度转到低精度? |
| 性质 | 状态 / 格式 | 动作 / 过程 |
| 词性感觉 | 名词(「这是 FP16」) | 动词(「把它量化成 INT4」) |
精度是「用什么格式」,量化是「从高格式转到低格式」。
中文里两者都常被说成「降低精度」,但降低精度是量化的结果,不是量化本身。精度是静态属性——一个模型文件存下来是什么格式,就一直是什么格式;量化是一次性操作——跑完得到一个全新的、低精度的文件。
收益、代价,和真实数字
收益很直接:权重文件更小,加载权重所需的内存更少,笔记本也能跑起来。
代价是「降智」:
推理的本质是大量的矩阵运算,如果权重矩阵中数字的精度降低,那么计算结果难免有偏差,体现出来的就是模型的回答质量下降。
注意速度不一定会提升——要看硬件和推理引擎的支持情况。4 bit 量化不一定比 8 bit 量化快。
实测数字(Qwen3-0.6B):
- Hugging Face 官方仓库的 BF16 权重:1.50GB
- Ollama 的
Q4_K_M模型包:523MB
理论上 4 bit 应该是 16 bit 的 1/4,约 375MB,但实际是 523MB。差异来自三处:量化要额外记录缩放数据(scale 等辅助信息);部分张量会保留更高精度;有的参数量按「不重复」算,有的按「实际存了多少个数」算。
Q4_0、Q4_K_M、Q8_0是不同的量化方案,名字里能看出大致位数和算法。具体好不好用要看发布者的说明,最好在自己的任务和硬件上实测。
四、选型:把模型跑起来
同一套权重,四个角度问四件事
刚开始看这些词会以为是一堆并列的模型类型,其实不是。
| 术语 | 它解决的问题 | 例子 |
|---|---|---|
| checkpoint | 保存的是哪个训练时刻的模型或训练状态 | checkpoint-2000、最终权重 |
| 精度和量化 | 权重里的数字如何表示 | BF16、FP16、Q4_0、Q4_K_M |
| 文件格式 | 权重参数和元数据如何保存 | Safetensors、GGUF |
| Hugging Face Hub | 模型发布和版本管理 | 模型页、Model Card、License |
这四层可以自由组合,所以同一条链能一路走到底:
BF16 权重→存成 Safetensors→转成 GGUF→量化成 Q4_K_M→本地引擎加载
于是看到7B · Q4_K_M · GGUF这种标签就能读懂了:约 70 亿参数,4 bit 为主的量化方案,组织成适合本地推理工具读取的 GGUF 单文件。
两种文件格式的差别:
- Safetensors只装张量 + 一份描述清单,加载时不会执行任何东西。早年
.bin权重用 pickle 序列化,加载时可能执行文件里夹带的代码,这是它被换掉的原因。模型结构和 tokenizer 通常仍放在单独的 JSON 文件里。 - GGUF把架构、tokenizer、量化类型全打包进一个文件,面向本地推理工具。llama.cpp、LM Studio、Ollama 直接加载它。
下载模型到底是下载什么
从 Hugging Face 下载模型,往往不是下载一个能双击运行的程序,而是下载一组文件,交给推理框架共同解释。
| 文件类别 | 对应文件 | 作用 |
|---|---|---|
| 权重文件 | model.safetensors | 保存模型学到的所有参数 |
| 配置文件 | config.json、generation_config.json | 说明模型结构和默认生成配置 |
| tokenizer 文件 | tokenizer.json、tokenizer_config.json、merges.txt、vocab.json | 定义文字和 token 如何互相转换 |
config.json里每个字段都和原理一一对应:
| 字段 | 值(Qwen3-0.6B) | 对应概念 |
|---|---|---|
vocab_size | 151,936 | embedding 矩阵的行数 |
hidden_size | 1,024 | embedding 矩阵的列数 |
num_attention_heads | 16 | 多头头数 |
num_hidden_layers | 28 | 层要叠的遍数 |
max_position_embeddings | 40,960 | 上下文长度上限 |
这两个 JSON 文件都很小,但少了它们,权重就只是一堆没法组装的数字。
选型时先看模型卡,别只看参数量
模型卡(README.md)通常写清楚这是 Base / Instruct / Chat、支持的语言和任务、训练数据、评测结果、已知限制、许可证、推荐推理框架和提示词格式。
但模型名称和参数量不能完整说明能力:
- 同规模的不同模型、同模型的不同微调版本,实际表现差距可能很大
- 同一个名字里的数字,数法可能不同:Qwen3 的
0.6B按模型逻辑上不重复的参数算,Hugging Face 页面统计的0.8B按权重文件里实际存了多少个数算——两个数都没错 - 小模型在模型卡上宣传的推理、Agent 能力够不够用,得拿自己的任务实测
本机跑:先算内存账
ollama pull qwen3:0.6b ollama run qwen3:0.6b ollama list# 磁盘上有什么包ollama show qwen3:0.6b# 模型的能力上限ollamaps# 这一次占了多少内存ollama stop qwen3:0.6b# 卸内存,不删模型ollamarmqwen3:0.6b# 才删磁盘关键是别把这三个数混起来:
| 命令 | 看什么 |
|---|---|
ollama list | 磁盘包。qwen3:0.6b → 522 MB |
ollama show | 架构上限:context length 40960、量化 Q4_K_M |
ollama ps | 本次加载后的 RAM:SIZE / PROCESSOR / CONTEXT / UNTIL |
ollama ps的 SIZE 公式是:
SIZE ≈ 权重文件大小 + 按 CONTEXT 预分配的 KV cache
注意「预分配」三个字。Ollama 在加载模型时按 CONTEXT 一次性划一整块 KV 内存,之后只说一句 “hi” 或者聊 300 轮,这块地都已经占着,SIZE 不会因为对话变长而增长。
实测:qwen3:0.6b的 KV cache 约 115KB/token(FP16),--ctx-size 32768时
115KB × 32768 ≈ 3.7GB KV + 522MB 权重 ≈ 4.2GB,加上开销对上 SIZE4.4 GB
这里有两个「上下文」要分清:
ollama show的 40960 是能力上限——这块地理论上能划多少车位ollama ps的 CONTEXT 是这一次实际划了几格,Ollama 默认往往只给 2048 或 4096
要长窗口得显式说:ollama run qwen3:0.6b --ctx-size 32768。
本机 HTTP 接口:
curlhttp://localhost:11434/v1/chat/completions\-H"Content-Type: application/json"\-d'{"model":"qwen3:0.6b","messages":[{"role":"user","content":"你好"}],"stream":false}'基址是http://localhost:11434/v1,字段名和云端兼容,reasoning_effort: "none"关思考。默认无鉴权——不要把 11434 暴露到公网。
一个现实的门槛
接口通了不等于能用它干活。要接 Coding Agent,官方文档建议上下文 ≥64K;qwen3:0.6b的上限是 40960,连门槛都不到。笔记本级硬件能跑本地模型的通常都是小模型,很难真正驱动 Coding Agent。
小结
| 环节 | 总结 |
|---|---|
| 训练 | 改参数,把随机数变成有用的数;产物是模型文件 |
| 推理 | 只读参数;每次 API 调用都是一次推理 |
| 微调 | Base 会接话,Instruct 会答话;微调从 Base 出发 |
| 精度 | 每个参数用多少位,是状态 |
| 量化 | 从高精度转到低精度,是动作 |
| 体积 | 参数量 × 每参数字节数,另外加 KV cache |
| 选型 | 看模型卡、看上下文、实测;参数量不等于能力 |