跳转至

第 7 章:模型导入——从 Safetensors/PyTorch 到统一 GGUF

一、转换器解决的不是“格式改名”

神经网络模型的差异有三类:

  1. 元数据差异:架构名、层数、context length、RoPE、tokenizer。
  2. tensor 命名差异:不同框架和模型家族使用不同权重 key。
  3. 布局差异:合并/拆分维度、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.goTensor 接口抽象输入 tensor;每种模型 converter 提供 KVTensorsReplacements 等能力。这样新增一个模型家族通常只需要实现自己的 metadata 和张量变换,而不必重写文件读取器。

三、模型家族的策略模式

目录里可以看到 convert_llama.goconvert_qwen3.goconvert_gemma4.goconvert_mistral.goconvert_deepseekocr.goconvert_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.gotokenizer/wordpiece.gox/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/:不让新格式的快速演进污染核心服务契约。