PostgreSQL码农集散地

ollama Modelfile 里的 TEMPLATE 是什么东西

背景

ollama是非常便捷的大模型管理套件, 支持API调用, 命令行调用等. 由于支持了API调用所以可以部署为服务, 同时结合其他软件例如前端应用实现简易的AI服务. (另外再介绍一个vLLM https://github.com/vllm-project/vllm ( A high-throughput and memory-efficient inference and serving engine for LLMs ) , 效率更胜一筹, 适合部署为对外服务, 但是易用性方面略输ollama, ollama更适合个人使用. )

ollama支持从Hugging Face导入模型, 从微调的适配文件导出模型等.

ollama支持转换模型精度, 减少内存的消耗.

以上参考 https://github.com/ollama/ollama/blob/main/docs/import.md

ollama还可以通过自定义大模型的模板文件modelfile, 通过ollama create实现更多场景的应用.

参考 https://github.com/ollama/ollama/blob/main/docs/modelfile.md

例如, 查看某个模型的modelfile

$ ollama list  
NAME                        ID              SIZE      MODIFIED       
deepseek-r1:7b              0a8c26691023    4.7 GB    23 hours ago      
deepseek-r1:14b             ea35dfe18182    9.0 GB    2 weeks ago       
mxbai-embed-large:latest    468836162de7    669 MB    2 months ago     

modelefile语法和dockerfile有点类似

$ ollama show --modelfile deepseek-r1:7b  
# Modelfile generated by "ollama show"  
# To build a new Modelfile based on this, replace FROM with:  
# FROM deepseek-r1:7b  

FROM /Users/digoal/.ollama/models/blobs/sha256-96c415656d377afbff962f6cdb2394ab092ccbcbaab4b82525bc4ca800fe8a49  
TEMPLATE """{{- if .System }}{{ .System }}{{ end }}  
{{- range $i, $_ := .Messages }}  
{{- $last := eq (len (slice $.Messages $i)) 1}}  
{{- if eq .Role "
user" }}<|User|>{{ .Content }}  
{{- else if eq .Role "
assistant" }}<|Assistant|>{{ .Content }}{{- if not $last }}<|end▁of▁sentence|>{{- end }}  
{{- end }}  
{{- if and $last (ne .Role "
assistant") }}<|Assistant|>{{- end }}  
{{- end }}"
""
PARAMETER stop <|begin▁of▁sentence|>  
PARAMETER stop <|end▁of▁sentence|>  
PARAMETER stop <|User|>  
PARAMETER stop <|Assistant|>  
LICENSE """MIT License  

Copyright (c) 2023 DeepSeek  

Permission is hereby granted, free of charge, to any person obtaining a copy  
of this software and associated documentation files (the "
Software"), to deal  
in the Software without restriction, including without limitation the rights  
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell  
copies of the Software, and to permit persons to whom the Software is  
furnished to do so, subject to the following conditions:  

The above copyright notice and this permission notice shall be included in all  
copies or substantial portions of the Software.  

THE SOFTWARE IS PROVIDED "
AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,  
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE  
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER  
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,  
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE  
SOFTWARE.  
"
""

在modelfile中比较难理解的部分是TEMPLATE, 详见

  • https://github.com/ollama/ollama/blob/main/docs/template.md

下面详细讲解这段内容,帮助理解 Ollama 模板引擎TEMPLATE的基本概念和用法。


1. 简介

Ollama 提供了一个强大的模板引擎,它基于 Go 语言内置的模板引擎,用于构造发送给大型语言模型(LLM)的提示(prompt)。这种模板机制可以让你更灵活、更高效地控制模型的输入格式,从而发挥模型的最大效能。


2. 基本模板结构

在 Go 模板中,一个基础模板通常包含三个主要部分:

  • 布局(Layout)
    模板的整体结构,即你如何组织和展示内容。

  • 变量(Variables)
    用于占位的标记(placeholders),在模板渲染时会被动态数据替换。比如文中的 Messages、Role、Content 等就是变量。

  • 函数(Functions)
    可以嵌入自定义函数或逻辑,用来处理或转换模板中的数据。例如,文中使用 range 来遍历消息列表。

例如,下面这个简单的聊天模板示例展示了如何遍历消息列表并输出每条消息的角色和内容:

{{- range .Messages }}  
{{ .Role }}: {{ .Content }}  
{{- end }}  

在这个示例中:

  • 模板使用了一个名为 Messages 的变量,它通常是一个消息列表。
  • range .Messages 是一个内置函数,用来遍历每一条消息。
  • 每条消息有两个字段:Role(角色,如 user、assistant 等)和 Content(消息内容)。

3. 如何在模型中添加模板

默认情况下,导入到 Ollama 的模型会使用一个默认模板 {{ .Prompt }},这意味着用户输入会原样传递给语言模型。这种方式适用于文本或代码补全模型,但对于聊天或指令模型来说,就缺少了必要的标识信息。

为了获得更好的效果,用户可以自己定义模板,把模型输入组织得更加清晰。这时,需要在模型的 Modelfile 中添加一个 TEMPLATE 命令。例如,下面是使用 Meta 的 Llama 3 模型的一个示例:

FROM llama3.2

TEMPLATE """{{- if .System }}<|start_header_id|>system<|end_header_id|>  

{{ .System }}<|eot_id|>  
{{- end }}  
{{- range .Messages }}<|start_header_id|>{{ .Role }}<|end_header_id|>  

{{ .Content }}<|eot_id|>  
{{- end }}<|start_header_id|>assistant<|end_header_id|>  

"
""

这个例子中:

  • 如果存在系统消息(System),则先输出系统提示,并用特殊标记包围。
  • 然后遍历所有消息(Messages),依次输出每条消息的角色和内容,并使用特殊标记分隔。
  • 最后指定一个默认的 assistant 标识。

4. 模板中的变量详解

模板中定义了一些变量,它们对应不同的输入和输出部分,主要包括:

  • System(字符串):系统提示,用于给模型提供背景信息或初始指令。
  • Prompt(字符串):用户的提示或问题。
  • Response(字符串):模型的回复。
  • Suffix(字符串):在模型回复后附加的文本。
  • Messages(列表):消息列表,每条消息包含以下属性:
    • ToolCalls[].Function(对象):要调用的函数。
    • Function.Name(字符串):函数名称。
    • Function.Arguments(映射):函数参数,键值对形式。
    • Messages[].Role(字符串):消息角色,可以是 system、user、assistant 或 tool。
    • Messages[].Content(字符串):消息内容。
    • Messages[].ToolCalls(列表):模型可能调用的工具列表,包含具体的调用细节。
  • Tools(列表):模型可以调用的工具列表,每个工具包含:
    • Tools[].Type(字符串):固定为 function。
    • Tools[].Function(对象):工具的定义,包括名称、描述和参数信息。

5. 使用模板时的小技巧与最佳实践

在编写和使用 Go 模板时,有一些注意事项和最佳实践:

  • 注意点(dot)的变化
    像 range 和 with 这样的控制结构会改变当前上下文中的 “点”(.),因此在需要引用根级变量时,可以使用 $. 来从根节点开始引用。

  • 超出作用域的变量
    使用 $. 来引用当前作用域之外的变量,确保在嵌套结构中仍能访问到根级数据。

  • 空白字符控制
    使用 -(例如 {{- 和 -}})可以自动去除前后不必要的空白字符,使生成的文本更加紧凑。


6. 模板示例

6.1 ChatML 模板示例

ChatML 是一种流行的模板格式,适用于多种聊天模型,如 Databrick 的 DBRX、Intel 的 Neural Chat 以及 Microsoft 的 Orca 2。示例模板如下:

{{- range .Messages }}<|im_start|>{{ .Role }}  
{{ .Content }}<|im_end|>  
{{ end }}<|im_start|>assistant  

这里,每条消息使用 <|im_start|> 和 <|im_end|> 特殊标记来包围,并标明角色,最后默认以 assistant 的身份开始回复。

6.2 工具调用模板示例

部分模型支持调用外部工具,比如 Mistral 和 Mixtral 8x22B。模板中通过添加 {{ .Tools }} 节点来实现工具支持,示例如下:

{{- range $index, $_ := .Messages }}  
{{- if eq .Role "user" }}  
{{- if and (le (len (slice $.Messages $index)) 2) $.Tools }}[AVAILABLE_TOOLS] {{ json $.Tools }}[/AVAILABLE_TOOLS]  
{{- end }}[INST] {{ if and (eq (len (slice $.Messages $index)) 1) $.System }}{{ $.System }}  

{{ end }}{{ .Content }}[/INST]  
{{- elseif eq .Role "assistant" }}  
{{- if .Content }} {{ .Content }}</s>  
{{- elseif .ToolCalls }}[TOOL_CALLS] [  
{{- range .ToolCalls }}{"name": "{{ .Function.Name }}", "arguments": {{ json .Function.Arguments }}}  
{{- end }}]</s>  
{{- end }}  
{{- elseif eq .Role "tool" }}[TOOL_RESULTS] {"content": {{ .Content }}}[/TOOL_RESULTS]  
{{- end }}  
{{- end }}  

这个模板根据消息的角色不同(user、assistant、tool),分别以不同的格式输出内容,并在合适的地方插入工具调用信息或系统提示。

在这段代码中,$_ 是一个“占位符”变量。在 Go 模板的 range 语句中,可以同时接收两个值,第一个通常是索引(这里用 $index 表示),第二个是当前迭代的元素。如果你不需要使用这个元素的值,可以将它赋值给一个“空”变量,通常约定用 $_ 来表示(类似于 Go 语言中的空白标识符 _),表明这个值将被忽略。

因此,代码写作

{{- range $index, $_ := .Messages }}  

的意思是:对 .Messages 列表进行迭代,将当前元素的索引存入 $index,而将当前元素的值赋给 $_(不过后续代码中并不会用到它,而是直接通过 . 来引用当前迭代的值)。这种写法既能取得索引,又明确表示不需要单独保存元素的值。

这种用法在模板中非常常见,有助于让代码更清晰,表明你只关心索引而不需要那个被迭代的值。

6.3 Fill-in-Middle 模板示例

Fill-in-middle 模板适用于生成文本插入在用户输入中间的场景(如代码补全)。例如 CodeLlama 模型的模板为:

<PRE> {{ .Prompt }} <SUF>{{ .Suffix }} <MID>  

这里模板将用户的 Prompt 放在前面,Suffix 放在后面,中间用 <MID> 表示生成内容插入的位置。注意,不同模型对 fill-in-middle 的支持情况可能不同,例如 CodeLlama 的 34B 和 70B 模型就不支持此功能。

另外,Codestral 模型也有类似的支持,示例为:

[SUFFIX]{{ .Suffix }}[PREFIX] {{ .Prompt }}  

总结

这段内容主要介绍了 Ollama 模板引擎的功能与结构,涵盖了以下几个方面:

  1. 模板引擎简介:利用 Go 内置模板引擎构造 LLM 的输入提示。
  2. 基本结构:包括模板的布局、变量和函数。
  3. 如何在模型中添加模板:通过在 Modelfile 中添加 TEMPLATE 命令来自定义模型输入格式。
  4. 变量详细说明:列举了模板中常用的变量和数据结构。
  5. 最佳实践:如何处理作用域、空白字符等细节问题。
  6. 实际示例:展示了不同场景下的模板写法,如 ChatML、工具调用和 Fill-in-middle 模板。

希望这个中文讲解能帮助你更好地理解和使用 Ollama 提供的模板引擎。如果有其他问题,欢迎继续提问。

如果你对template的语法还不太熟悉, 但是想自定义Modelfile, 也可以找AI帮忙.

参考

《AI大模型+全文检索+tf-idf+向量数据库+我的文章 系列之8 - 大模型(LLM)常见参数设置, 影响性能和可靠性》

《AI大模型+全文检索+tf-idf+向量数据库+我的文章 系列之6 - 科普 : 大模型到底能干什么? 如何选型? 专业术语? 资源消耗?》

《OLLAMA 环境变量/高级参数配置 : 监听 , 内存释放窗口 , 常用设置举例》