第 7 章:模型导入——从 Safetensors/PyTorch 到统一 GGUF¶
一、转换器解决的不是“格式改名”¶
神经网络模型的差异有三类:
- 元数据差异:架构名、层数、context length、RoPE、tokenizer。
- tensor 命名差异:不同框架和模型家族使用不同权重 key。
- 布局差异:合并/拆分维度、QKV 排布、MoE experts、视觉 projector。
convert/ 的职责就是把这些差异变成 GGUF 所需的 metadata 和 tensor layout。它不是服务请求路径的必经步骤,而是“模型进入 Ollama 运行时之前”的构建路径。
二、转换主线¶
输入目录/文件
→ reader(safetensors / torch / json)
→ tokenizer / vocabulary
→ 模型架构探测
→ family converter
→ KV metadata + tensor replacement/repack
→ GGUF writer
→ manifest + blob
convert/reader.go 用 Tensor 接口抽象输入 tensor;每种模型 converter 提供 KV、Tensors、Replacements 等能力。这样新增一个模型家族通常只需要实现自己的 metadata 和张量变换,而不必重写文件读取器。
三、模型家族的策略模式¶
目录里可以看到 convert_llama.go、convert_qwen3.go、convert_gemma4.go、convert_mistral.go、convert_deepseekocr.go、convert_lfm2.go 等大量 family-specific 文件。
它们共同表达一个模式:
type modelConverter interface {
KV(*Tokenizer) KV
Tensors([]Tensor) []*ggml.Tensor
Replacements() []string
}
具体接口名称以快照为准,但设计意图很稳定:metadata 和 tensor 变换是可替换策略。
四、Tokenizer 不能被当作附属文件¶
转换阶段需要把 tokenizer 的 vocabulary、special tokens、BOS/EOS 等写入 GGUF metadata;运行时又要通过这些信息进行 tokenize/detokenize、prompt 长度计算和模板渲染。
因此 convert/tokenizer.go、tokenizer/wordpiece.go、x/tokenizer/ 与 server 的 context 逻辑实际是连着的:
tokenizer metadata
↓
GGUF KV
↓
server.Model / llm.LoadModel
↓
Tokenize + prompt budget + truncation
如果转换时 special token 错了,运行时可能表现为模板边界错、EOS 不停止、tool call 无法识别,而不是一个直观的“转换失败”。
五、Tensor repack 的意义¶
convert/tensor.go 里有 split、merge、repack 等通用操作;它们把输入框架的 tensor 变成 ggml/llama-server 需要的布局。
常见场景包括:
- QKV 合并或拆分。
- attention/MLP 投影矩阵重排。
- MoE expert 权重按 layer 重新组合。
- 多模态模型的 projector 单独写入。
- 旧模型命名兼容与 key replacement。
这也是为什么“直接把 safetensors 文件交给 runner”通常不成立:推理后端需要稳定的 metadata、内存布局和量化信息。
六、Safetensors 实验路径¶
快照中 x/create/ 提供实验性的 Safetensors 模型创建能力,CLI 的 CreateHandler 会解析 Modelfile,判断 --experimental,再把模型目录和 draft/quantize 选项交给 x/create/client。
ollama create --experimental
→ parser.ParseFile(Modelfile)
→ ConfigFromModelfile
→ Safetensors model dir
→ create/quantize pipeline
→ local model registration
这是一个值得观察的架构信号:主服务仍以 GGUF/llama-server 为稳定路径,新的模型格式先被隔离在 x/,通过边界逐步接入。
七、为什么测试很多¶
模型转换最怕“能跑但错了”:tensor shape 看似正确,输出却悄悄变差。因此 convert/*_test.go 大量验证 metadata、tensor 数量、repack 结果、tokenizer 和 JSON 兼容性。
读 converter 时,测试往往比实现更能说明不变量:哪些 key 必须存在、哪个形状需要转置、哪个特殊 token 需要补齐。
八、设计取舍¶
- 按模型家族拆文件:重复一些 boilerplate,换来局部可理解性。
- 通用 reader + 专用 converter:格式读取复用,架构差异显式化。
- 转换到 GGUF 再运行:牺牲即时导入,换来稳定加载、量化与 native backend 兼容。
- 实验能力放进
x/:不让新格式的快速演进污染核心服务契约。