看懂 Hugging Face 和模型文件

我们已经用 Ollama 在本地运行了 Qwen3 0.6B。运行命令很简单,但只要开始寻找其他模型,很快就会遇到一连串陌生词:Model Card、checkpoint、BF16、Q4_K_M、Safetensors、GGUF 等等。

新概念比较多,有的表示模型托管平台,有的表示训练状态,有的描述数字精度,还有的只是保存权重的文件格式。

这篇文章从 Qwen 官方的 Hugging Face 模型页面出发,先看清一个模型仓库里有哪几类文件,再把这些新词逐个挂到对应的文件上,它们就不难理解了。

Hugging Face 到底是什么

Hugging Face Hub 可以理解成面向 AI 模型的 GitHub。开发者可以在上面发布模型仓库、保存文件版本、展示使用文档、声明许可证,也可以让 Transformers、Ollama 等工具下载这些文件。

一个模型页面最常用的两部分是:

  • Model Card(模型卡):类似项目的 README,说明模型是谁训练的、能做什么、不能做什么、如何使用以及采用什么许可证。
  • Files(文件):模型真正的交付物,包括权重、模型结构配置和 tokenizer 等。

打开 Qwen3 0.6B 的官方页面,右侧信息卡上有两个值得留意的字段:Model size 0.8B paramsTensor type BF16

名字里写的是 0.6B,页面却统计出 0.8B。两个数都没错,只是数法不同:名字按模型逻辑上有多少个不重复的参数算,页面按权重文件里实际存了多少个数算。

BF16 是另一个维度的事,它表示权重保存时每个数字的精度,后面讲精度时再展开。

读懂模型卡

模型名称和参数量不能完整说明能力。模型卡通常还会写清楚:

  • 这是 Base 模型、Instruct 模型还是 Chat 模型。
  • 主要支持哪些语言和任务。
  • 训练数据、评测结果和已知限制。
  • 许可证是否允许自己的使用场景。
  • 推荐使用哪个推理框架和提示词格式。

Qwen3 的模型卡就把这些交代得很清楚:支持思考模式与非思考模式、原生上下文多长、怎么开关思考输出。

当然,0.6B 毕竟是很小的模型,模型卡上宣传的推理、Agent 这些能力到底够不够用,还得拿自己的任务实测。

名称中的 BaseInstruct 的区别,LLM 和 Agent 的核心术语和原理里专门讲过:Base 模型是纯粹的文本补全模型,只会「接话」,一般用作继续预训练或微调的起点;Instruct 模型在它基础上做过指令对齐,会把你的输入当成问题来回答。

所以日常聊天要选 InstructChat 版本。Qwen3 0.6B 的官方仓库带有对话模板并支持 conversational,Ollama 可以直接把用户消息组织成适合它的输入。

一个 LLM 由哪几类文件组成

切换到 Files 页签,可以看到模型仓库的实际内容。为了让文件列表在未来仍能复现,下面固定到 revision c1899de。主要文件包括:

LICENSE
README.md
config.json
generation_config.json
merges.txt
model.safetensors
tokenizer.json
tokenizer_config.json
vocab.json

文件不少,先做个分类,README.md 就是网页上展示的模型卡,LICENSE 记录授权条款,这两个是给人看的说明文件。

剩下的文件才是模型本体,可以归成三类:

文件类别对应文件作用
权重文件model.safetensors保存模型学到的所有参数
配置文件config.jsongeneration_config.json说明模型结构和默认生成配置
tokenizer 文件tokenizer.jsontokenizer_config.jsonmerges.txtvocab.json定义文字和 token 如何互相转换

所以「从 Hugging Face 下载模型」往往不是下载一个能双击运行的程序,而是下载这样一组文件,交给推理框架共同解释:权重提供模型学到的数字,配置说明如何搭建网络,tokenizer 负责在文字和 token 之间转换。

之前用 Ollama 在本地运行大模型时,ollama run 一条命令就把这些事全办了,所以你只看到一个对话框。下面挨个打开看看,它们各自装着什么。

权重文件:模型学到的参数

model.safetensors 有 1.50GB,比其他文件加起来还大得多,因为它保存了训练得到的全部参数,也就是模型权重。

权重是模型的本体,平时说「下载模型」,主要就是在下载它。这 1.50GB 的权重其实就是一块块张量(tensor)

张量就是「多维数组」在机器学习里的叫法,每块都带一个形状(shape),说明张量有几个方向、每个方向多长,看几个例子:

       shape        tensor

1D     [3]          [0.1, 0.2, 0.3]

2D     [2, 3]       [[0.1, 0.2, 0.3],
                     [0.4, 0.5, 0.6]]

3D     [2, 2, 3]    [[[0.1, 0.2, 0.3],
                      [0.4, 0.5, 0.6]],
                     [[0.7, 0.8, 0.9],
                      [1.0, 1.1, 1.2]]]

说白了张量就是多维数组,形状为 [3] 的张量就是长度为 3 的一维数组,形状为 [2,3] 的张量就是 2 行 3 列的二维数组(矩阵)。权重文件里基本都是一维和二维张量,三维以上要到成批处理数据时才常见。

明白了这个名词,来看 model.safetensors 文件。文件开头是一份清单,记着里面每个张量叫什么、形状多大,Qwen3 0.6B 一共列了 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]

第一行 embed_tokens 就是 LLM 是怎么预测下一个 token 的 里讲过的 embedding 矩阵,形状 [151936, 1024] 说明它是个二维张量:151,936 行是词表容量,每个 token 占一行,每行 1,024 个数。

后面几行是注意力和前馈网络的权重,名字里的 layers.0layers.27 是层号,这款模型一共 28 层。

清单后面才是正文,也就是每个张量的具体数字。把 embedding 矩阵铺开,大致长这样:

model.embed_tokens.weight   [151936, 1024]

                col 0     col 1     col 2     ...  col 1023
row 0        -0.00928   0.03369  -0.07471     ...   0.01599
row 1         0.03198   0.02380  -0.05933     ...   0.00903
   ...
row 14990    -0.04517   0.00081   0.01868     ...  -0.00952
   ...
row 151935    0.00604   0.01312   0.01904     ...   0.00568

151,936 乘以 1,024 是 1.56 亿个数,每个数占 2 byte,光这一张表就占 311MB。多个张量累加起来,就凑出了 1.50GB。

模型「学到的东西」就是这样一堆二维矩阵,没有一个是人写的,全是靠训练时一步步调出来的。

如果模型的权重太大,还可能被拆成 model-00001-of-00004.safetensors 等多个分片,并由索引文件记录每个张量在哪个分片中。这些分片合起来才是一套完整的模型权重。

配置文件:模型结构的说明书

光有一大堆参数还不够,推理框架得知道怎么把这些数字组装成一个神经网络,这就是 config.json 的活儿。节选几行:

{
  "num_hidden_layers": 28,
  "num_attention_heads": 16,
  "hidden_size": 1024,
  "vocab_size": 151936,
  "max_position_embeddings": 40960
}

这几个数字在LLM 是怎么预测下一个 token 的里都照过面:

vocab_sizehidden_size 正是 embedding 矩阵的 151,936 行、1,024 列;num_attention_heads 是多头注意力的头数;num_hidden_layers 说明这样的层要叠 28 遍;max_position_embeddings 是位置编码能覆盖的最大 token 数,也就是上下文长度的上限。推理框架照着这些数字把骨架搭起来,再把权重文件里同名的矩阵填进去。

generation_config.json 管的是另一件事,生成文本时的默认采样参数,同样节选几行:

{
  "temperature": 0.6,
  "top_p": 0.95,
  "top_k": 20
}

这三个值应该眼熟,ollama show qwen3:0.6b 显示的就是它们,模型作者调好写在这里,转成 Ollama 用的格式时一起带了过去。你不指定采样参数时,用的就是这一套。

这两个都是很小的 JSON 文件,但少了它们,权重就只是一堆没法组装的数字。

tokenizer 文件:文字和 token 的转换

LLM 是怎么预测下一个 token 的里讲过,模型不直接处理文字,输入要先被切成 token,输出的 token 也要再拼回文字。

那篇文章拆过的两个文件就在这里:vocab.json 是词表,记着哪个 token 对应哪个编号;merges.txt 是 BPE 合并规则,一共 151,387 条,按训练时学到的先后顺序排列。

另外两个文件的角色不一样:

  • tokenizer.json 可以理解成 vocab.jsonmerges.txt 的合并升级版:同样的词表和合并规则装进一个文件,再补上切分流程的其余规则,比如标点怎么预切分、有哪些特殊 token、token 怎么解码回文字。它是如今 tokenizer 库实际读的那个;vocab.jsonmerges.txt 这两个老文件的内容跟它完全重合,留着是给旧版实现读的。
  • tokenizer_config.json 里没有完整词表,只有几 KB,记的是这个 tokenizer 该怎么用:结束符是哪个 token、最长支持多少 token,还有前面提过的对话模板——把「谁说了什么」拼成模型认识的格式,<|im_start|><|im_end|> 这些标记就是在这里定义的。

它们加起来也就十几 MB,和权重文件完全不是一个量级。

其中的对话模板值得多说两句,它能解释一个常见疑问:模型只会一个接一个地续写 token,多轮对话和「思考过程」是怎么冒出来的?

答案全在那几个标记上。你问一句「1+1 等于几?」,tokenizer 会先按模板把它拼成这样,再交给模型:

<|im_start|>user
1+1 等于几?<|im_end|>
<|im_start|>assistant

模型顺着这段文字往下续写,思考和回答一并吐出来,中间用标记隔开:

<think>
好的,用户问的是“1+1等于几?”。首先,我需要确定用户的问题类型。这是一个常见的数学问题……
</think>

1加上1等于2。这是一个基本的加法运算,无需额外步骤。如果你需要更多数学练习或帮助,请随时告诉我。<|im_end|>

推理框架收到的就是这么一串连续文本,它按 </think> 切一刀,前半段作为思考内容,后半段作为正式回复,看到 <|im_end|> 就停止生成。

你在 API 响应里看到的「思考过程」和「正式回复」两个字段,就是这么分出来的。模型自己并不区分这两者,它只是在续写 token 而已

用 Ollama 在本地运行大模型里用过的那个接口问同一个问题,看到的就是切好之后的样子:

curl --silent http://localhost:11434/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "qwen3:0.6b",
    "messages": [{"role": "user", "content": "1+1 等于几?"}],
    "stream": false
  }' | jq '.choices[0]'

本机实测返回:

{
  "index": 0,
  "message": {
    "role": "assistant",
    "reasoning": "好的,用户问的是“1+1等于几?”。首先,我需要确定用户的问题类型。这是一个常见的数学问题……",
    "content": "1加上1等于2。这是一个基本的加法运算,无需额外步骤。如果你需要更多数学练习或帮助,请随时告诉我。"
  },
  "finish_reason": "stop"
}

response 中 reasoning 字段是 <think></think> 之间的部分,content</think> 之后的部分,finish_reasonstop 表示生成是撞上 <|im_end|> 正常结束的。

这套标记只是 Qwen 选的约定,换个模型可能完全不同。比如 DeepSeek-R1 用的是 <|User|><|Assistant|>。所以同一段对话喂给不同模型,拼出来的字符串并不一样,这也是每个模型仓库都得自带一份对话模板的原因。

其他术语

三类文件认全了,再回头看开头那串新词:checkpoint、BF16、Q4_K_M、Safetensors、GGUF。

它们看起来杂,其实都在描述权重文件,只是角度不同:

  • checkpoint 说的是「这是哪个时刻保存下来的权重」。
  • 精度和量化说的是「权重里的数字用多少 bit 表示」。
  • Safetensors 和 GGUF 说的是「权重怎么装进文件里」。

checkpoint:权重存档

训练模型的过程要持续调整大量参数。为了防止中断后从头再来,训练程序会定期保存当时的状态,这份存档叫 checkpoint(检查点)

一个完整的训练 checkpoint 可能包含:当前模型权重、优化器状态、已完成的训练步数等等信息。

有了这些内容,训练程序才能尽量从中断位置继续。

训练结束后发布的最终模型权重,也经常被简称为 checkpoint。它表达的是「模型在某个时刻的参数状态」,不是某一种固定的文件扩展名。

如果仓库中出现 checkpoint-1000checkpoint-2000,通常表示训练到不同步数时保存的版本。

普通使用者一般应该选择作者明确发布的最终版本,而不是随便挑一个中间存档,因为中间存档可能训练不到位,效果不理想。

checkpoint 中的模型权重可以保存成 Safetensors 格式,也可以转换成 GGUF 格式。GGUF 是本地推理工具通用的格式,两种格式的区别后面会展开讲。

精度:每个参数用多少 bit

模型权重本质上是一大组数字,精度决定每个数字用多少 bit、采用什么数值表示。常见的有三种:

类型每个参数常见占用特点
FP3232 bit,也就是 4 byte精度高、占用大,常作为高精度基准
FP1616 bit,也就是 2 byte文件和显存约为 FP32 的一半,常用于训练和推理
BF1616 bit,也就是 2 byte位数与 FP16 相同,但数值范围更接近 FP32

前面算 embedding 表占 311MB 时,用的就是 BF16 的每个数 2 byte。同一套权重如果改用 FP32 保存,每个数变成 4 byte,整个文件就从 1.50GB 翻倍到 3GB 左右。

当然,这里只是计算权重文件的磁盘占用大小,真正加载到内存里跑起来还要加上上下文缓存、临时张量和推理程序本身的开销,内存占用会比模型文件大不少。

量化:让模型变小

量化把高精度数字近似成更低精度的表示,以减少文件和内存占用。它会把原本 16 bit 或 32 bit 的权重近似成 8 bit、4 bit。

位数降低后,最直接的好处是 权重文件更小,加载权重所需的内存也就更少,资源有限的机器(比如笔记本电脑)也能跑的起来

至于推理速度是否能提升,要看硬件和推理引擎的支持情况,4 bit 量化不一定就比 8 bit 量化快。

代价是精度降低后的近似误差可能让回答质量下降。因为推理的本质其实就是大量的矩阵运算,如果权重矩阵中数字的精度降低,那么计算结果难免有偏差,体现出来的就是模型的回答质量下降,也就是我们常说的「降智」。

上图 model 那一行末尾的 quantization Q4_K_M,就是一种以 4 bit 为主的量化方案。

Hugging Face 官方仓库里的 BF16 权重文件是 1.50GB,而这个 Q4_K_M 模型包只有 523MB。

理论上 4 bit 量化后的权重大小应该是 16 bit 权重大小的 1/4,但实际 523MB 比 1.50GB 的 1/4 要大。因为量化要额外记录缩放数据,且部分张量需要保留更高精度,所以实际会比 1/4 大一些。

Q4_0Q4_K_MQ8_0 是不同的量化方案,名字里能看出大致位数和算法。具体好不好用,要看发布者的说明,最好在自己的任务和硬件上实测。

Safetensors 和 GGUF

最后两个词,回答的是「权重和配套元数据如何组织进文件」。

Safetensors 是专门保存张量的格式,重点是安全和快速。早年常见的 .bin 权重用 Python pickle 序列化,加载时可能执行文件里夹带的代码;Safetensors 文件里只有张量数据和一份描述它们的清单,加载时不会执行任何东西,读取也更高效。

Hugging Face Transformers 生态经常用它保存原始或微调后的权重;模型结构和 tokenizer 通常仍放在单独的 JSON 文件中和权重文件分开存储。

GGUF 是本地推理生态常见的二进制格式。

它不仅保存张量数据,还能把模型架构、tokenizer 等标准化元数据放进同一个文件,并支持多种量化类型,相当于把运行大模型所需的所有文件打包成一个文件。

llama.cpp、LM Studio、Ollama 等本地推理工具可以直接加载 GGUF 文件进行推理,这也是 GGUF 格式经常和「本地运行模型」一起出现的原因。

总结

一个 LLM 模型仓库里最重要的是三类文件:权重保存模型学到的参数,配置说明网络结构,tokenizer 负责文字和 token 的转换。

剩下的新词都在描述权重:checkpoint 是某个训练时刻的权重存档,FP16、BF16 和各种量化方案描述权重里的数字如何表示,Safetensors 与 GGUF 则描述权重如何保存在文件里。

总结成一张表格:

术语它解决的问题例子
Hugging Face Hub模型发布和版本管理模型页、Model Card、License
checkpoint保存的是哪个训练时刻的模型或训练状态checkpoint-2000、最终权重
精度和量化权重参数中的数字如何表示BF16、FP16、Q4_0、Q4_K_M
文件格式权重参数和元数据如何保存Safetensors、GGUF

这几层概念可以自由组合:一套 BF16 模型权重可以以 Safetensors 格式保存,也可以转换为 GGUF,再量化成 Q4_0 或 Q4_K_M,由推理引擎加载运行。

理解这些层次后,再看到 7B · Q4_K_M · GGUF 这种标签,就能理解:这个模型大约有 70 亿参数,权重采用 4 bit 为主的量化方案,并被组织成适合本地推理工具读取的 GGUF 文件。

不过,模型文件自己不会回答问题。接下来可以继续阅读动手跑通 LLM 推理的全流程,用 Transformers 把配置、权重和 tokenizer 加载进 Python,亲眼看看它们怎样变成一次完整的模型回答。