以下个人没整理太全
一、生成式语言模型的对话模板介绍
使用Qwen/Qwen1.5-0.5B-Chat训练
对话模板不一样。回答的内容就会不一样。
我们可以看到例如qwen模型的tokenizer_config.json文件,就可以看到对话模板,一般同系列的模型,模板基本都一致。可以通过更改chat_template(对话模板)内容,来实现自己想要的对话模板。
如果我们使用open-webui来做前端显示,你会发现open-webui有自己的对话模板,他和我自己训练的qwen系列的大模型对话模板不一样,这就导致了,你用ollama跑qwen时回答的内容,和用open-webui做前端页面渲染回答的内容是不一致的。很奇葩,真的很奇葩。同时你会发现大模型微调框架、推理框架,以及像前端页面转发的open-webui他们的框架对话模板可能也是一样的。这就导致了,你明明在LLamaFactory微调框架的模板回答是自己想要的东西,但是在推理框架,比如vllm上就会不一样。头大吧。
对话的内容也会受一下参数控制,一般后两个参数都是统一的,唯一变化的就是最大生成长度。

这个问题可复现
比如我们在LLamaFactory,跑的是我自己训练过后的qwen模型。
使用vllm跑的自己训练过后的qwen模型
需要安装vllm(必须是Linux环境),不然前后台都会报错。
bash
pip install vllm>=0.4.3,<=0.7.3 -i https://pypi.tuna.tsinghua.edu.cn/simple

你会发现效果一致了。如果效果不一致,说明你的模型需要继续训练。
2.Lora微调后单独部署大模型输出结果不一致

使用vllm serve启动自己训练的模型
bash
vllm serve 你的模型
然后就可以通过后端代码调用对话了。
你会发现其实会有明显的差距,说明模型训练还未收敛,你需要继续训练,让大模型继续学习。
3.如何导出LLama Factory的对话模板
同时我们可以看一下LLamaFactory源码下的qwen对话模板
源码搜索就可以找到了
你会发现LLamaFactory整合了同一系列的对话模板。
LLamaFactory找到这个jinja模板
我们可以看到他是一个私有化的方法,外部没法调用,但是可以调用
fix_jinja_template生成jinja模板。那么就需要我们自己写代码了。可以用ai工具生成。
bash
# mytest.py
import sys
import os
# 将项目根目录添加到 Python 路径
# root_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
# sys.path.append(root_dir)
from llamafactory.data.template import TEMPLATES
from transformers import AutoTokenizer
# 1. 初始化分词器(任意支持的分词器均可)
tokenizer = AutoTokenizer.from_pretrained(r"D:\Program Files\python\PycharmProjects\AiStudyProject\demo07\models\Qwen\Qwen2___5-1___5B-Instruct-merge")
# 2. 获取模板对象
template_name = "qwen" # 替换为你需要查看的模板名称
template = TEMPLATES[template_name]
# 3. 修复分词器的 Jinja 模板
template.fix_jinja_template(tokenizer)
# 4. 直接输出模板的 Jinja 格式
print("=" * 40)
print(f"Template [{template_name}] 的 Jinja 格式:")
print("=" * 40)
print(tokenizer.chat_template)
就可以得到jinja得到qwen的path-to-chat-template.jinja模板,就可以给vllm用了。
bash
{%- if tools %}
{{- '<|im_start|>system\n' }}
{%- if messages[0]['role'] == 'system' %}
{{- messages[0]['content'] }}
{%- else %}
{{- 'You are Qwen, created by Alibaba Cloud. You are a helpful assistant.' }}
{%- endif %}
{{- "\n\n# Tools\n\nYou may call one or more functions to assist with the user query.\n\nYou are provided with function signatures within <tools></tools> XML tags:\n<tools>" }}
{%- for tool in tools %}
{{- "\n" }}
{{- tool | tojson }}
{%- endfor %}
{{- "\n</tools>\n\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\n<tool_call>\n{\"name\": <function-name>, \"arguments\": <args-json-object>}\n</tool_call><|im_end|>\n" }}
{%- else %}
{%- if messages[0]['role'] == 'system' %}
{{- '<|im_start|>system\n' + messages[0]['content'] + '<|im_end|>\n' }}
{%- else %}
{{- '<|im_start|>system\nYou are Qwen, created by Alibaba Cloud. You are a helpful assistant.<|im_end|>\n' }}
{%- endif %}
{%- endif %}
{%- for message in messages %}
{%- if (message.role == "user") or (message.role == "system" and not loop.first) or (message.role == "assistant" and not message.tool_calls) %}
{{- '<|im_start|>' + message.role + '\n' + message.content + '<|im_end|>' + '\n' }}
{%- elif message.role == "assistant" %}
{{- '<|im_start|>' + message.role }}
{%- if message.content %}
{{- '\n' + message.content }}
{%- endif %}
{%- for tool_call in message.tool_calls %}
{%- if tool_call.function is defined %}
{%- set tool_call = tool_call.function %}
{%- endif %}
{{- '\n<tool_call>\n{"name": "' }}
{{- tool_call.name }}
{{- '", "arguments": ' }}
{{- tool_call.arguments | tojson }}
{{- '}\n</tool_call>' }}
{%- endfor %}
{{- '<|im_end|>\n' }}
{%- elif message.role == "tool" %}
{%- if (loop.index0 == 0) or (messages[loop.index0 - 1].role != "tool") %}
{{- '<|im_start|>user' }}
{%- endif %}
{{- '\n<tool_response>\n' }}
{{- message.content }}
{{- '\n</tool_response>' }}
{%- if loop.last or (messages[loop.index0 + 1].role != "tool") %}
{{- '<|im_end|>\n' }}
{%- endif %}
{%- endif %}
{%- endfor %}
{%- if add_generation_prompt %}
{{- '<|im_start|>assistant\n' }}
{%- endif %}
那么接下来启动
bash
vllm serve 模型名称 --chat-template ./path-to-chat-template.jinja
最方便的方法是可以直接替换LLamaFactory里的tokenizer_config.json里的chat_template模板里的内容
四、vllm推理模型时自定义对话模板
一、vLLM聊天模板
-
作用:为了使语言模型支持聊天协议,vLLM要求模型在其tokenizer配置中包含聊天模板。
-
聊天模板定义 :聊天模板是一个Jinja2模板,用于指定角色、消息和其他特定于聊天的token如何在输入中编码。
可以在 NousResearch/Meta-Llama-3-8B-Instruct 的示例聊天模板可以在这里找到。
-
使用场景:
- 有些模型即使经过指令/聊天微调,也不提供聊天模板。对于这些模型,可以在
--chat-template
参数中使用聊天模板的文件路径或字符串形式的模板手动指定其聊天模板。 - 如果没有聊天模板,服务器将无法处理聊天,并且所有聊天请求都会出错。
- 有些模型即使经过指令/聊天微调,也不提供聊天模板。对于这些模型,可以在
-
使用方法:
bashvllm serve <model> --chat-template ./path-to-chat-template.jinja
-
模板来源 :vLLM社区为流行的模型提供了一组聊天模板,可以在
examples
目录下找到它们。
二、LMDeploy自定义对话模板
-
添加对话模板形式:
- 利用现有对话模板:直接配置一个json文件使用。
- 自定义Python对话模板类:注册成功后直接使用,优点是自定义程度高,可控性强。
-
json文件配置示例:
json
{
"model_name": "your awesome chat template name",
"system": "<|im_start|>system\n",
"meta_instruction": "You are a robot developed by LMDeploy.",
"eosys": "<|im_end|>\n",
"user": "<|im_start|>user\n",
"eoh": "<|im_end|>\n",
"assistant": "<|im_start|>assistant\n",
"eoa": "<|im_end|>",
"separator": "\n",
"capability": "chat",
"stop_words": ["<|im_end|>"]
}
model_name
为必填项,可以是LMDeploy内置对话模板名,也可以是新名字。- 其他字段可选填。当
model_name
是内置对话模板名时,json文件中各非null字段会覆盖原有对话模板的对应属性。 - 当
model_name
是新名字时,它会把BaseChatTemplate
直接注册成新的对话模板。
-
模板拼接形式:
{system}{meta_instruction}{eosys}{user}{user_content}{eoh}{assistant}{assistant_content}{eoa}{separator}{user}...
-
使用方法:
-
使用CLI工具时,可以通过
--chat-template
传入自定义对话模板。bashlmdeploy serve api_server internlm/internlm2_5-7b-chat --chat-template ${JSON_FILE}
-
通过接口函数传入。
pythonfrom lmdeploy import ChatTemplateConfig, serve serve('internlm/internlm2_5-7b-chat', chat_template_config=ChatTemplateConfig.from_json('${JSON_FILE}'))
-
-
自定义Python对话模板类示例:
python
from lmdeploy.model import MODELS, BaseChatTemplate
@MODELS.register_module(name='customized_model')
class CustomizedModel(BaseChatTemplate):
"""A customized chat template."""
def __init__(self,
system='<|im_start|>system\n',
meta_instruction='You are a robot developed by LMDeploy.',
user='<|im_start|>user\n',
assistant='<|im_start|>assistant\n',
eosys='<|im_end|>\n',
eoh='<|im_end|>\n',
eoa='<|im_end|>',
separator='\n',
stop_words=['<|im_end|>', '<|action_end|>']):
super().__init__(system=system,
meta_instruction=meta_instruction,
eosys=eosys,
user=user,
eoh=eoh,
assistant=assistant,
eoa=eoa,
separator=separator,
stop_words=stop_words)
from lmdeploy import ChatTemplateConfig, pipeline
messages = [{'role': 'user', 'content': 'who are you?'}]
pipe = pipeline('internlm/internlm2_5-7b-chat', chat_template_config=ChatTemplateConfig('customized_model'))
for response in pipe.stream_infer(messages):
print(response.text, end='')
LLamaFactory找到这个jinja模板,然后使用代码导出模板,然后给vllm做模板。
我们可以看到他是一个私有化的方法,外部没法调用,但是可以调用
fix_jinja_template生成jinja模板。那么就需要我们自己写代码了。可以用ai工具生成。
bash
# mytest.py
import sys
import os
# 将项目根目录添加到 Python 路径
# root_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
# sys.path.append(root_dir)
from llamafactory.data.template import TEMPLATES
from transformers import AutoTokenizer
# 1. 初始化分词器(任意支持的分词器均可)
tokenizer = AutoTokenizer.from_pretrained(r"D:\Program Files\python\PycharmProjects\AiStudyProject\demo07\models\Qwen\Qwen2___5-1___5B-Instruct-merge")
# 2. 获取模板对象
template_name = "qwen" # 替换为你需要查看的模板名称
template = TEMPLATES[template_name]
# 3. 修复分词器的 Jinja 模板
template.fix_jinja_template(tokenizer)
# 4. 直接输出模板的 Jinja 格式
print("=" * 40)
print(f"Template [{template_name}] 的 Jinja 格式:")
print("=" * 40)
print(tokenizer.chat_template)
就可以得到jinja得到qwen的path-to-chat-template.jinja模板,就可以给vllm用了。
bash
{%- if tools %}
{{- '<|im_start|>system\n' }}
{%- if messages[0]['role'] == 'system' %}
{{- messages[0]['content'] }}
{%- else %}
{{- 'You are Qwen, created by Alibaba Cloud. You are a helpful assistant.' }}
{%- endif %}
{{- "\n\n# Tools\n\nYou may call one or more functions to assist with the user query.\n\nYou are provided with function signatures within <tools></tools> XML tags:\n<tools>" }}
{%- for tool in tools %}
{{- "\n" }}
{{- tool | tojson }}
{%- endfor %}
{{- "\n</tools>\n\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\n<tool_call>\n{\"name\": <function-name>, \"arguments\": <args-json-object>}\n</tool_call><|im_end|>\n" }}
{%- else %}
{%- if messages[0]['role'] == 'system' %}
{{- '<|im_start|>system\n' + messages[0]['content'] + '<|im_end|>\n' }}
{%- else %}
{{- '<|im_start|>system\nYou are Qwen, created by Alibaba Cloud. You are a helpful assistant.<|im_end|>\n' }}
{%- endif %}
{%- endif %}
{%- for message in messages %}
{%- if (message.role == "user") or (message.role == "system" and not loop.first) or (message.role == "assistant" and not message.tool_calls) %}
{{- '<|im_start|>' + message.role + '\n' + message.content + '<|im_end|>' + '\n' }}
{%- elif message.role == "assistant" %}
{{- '<|im_start|>' + message.role }}
{%- if message.content %}
{{- '\n' + message.content }}
{%- endif %}
{%- for tool_call in message.tool_calls %}
{%- if tool_call.function is defined %}
{%- set tool_call = tool_call.function %}
{%- endif %}
{{- '\n<tool_call>\n{"name": "' }}
{{- tool_call.name }}
{{- '", "arguments": ' }}
{{- tool_call.arguments | tojson }}
{{- '}\n</tool_call>' }}
{%- endfor %}
{{- '<|im_end|>\n' }}
{%- elif message.role == "tool" %}
{%- if (loop.index0 == 0) or (messages[loop.index0 - 1].role != "tool") %}
{{- '<|im_start|>user' }}
{%- endif %}
{{- '\n<tool_response>\n' }}
{{- message.content }}
{{- '\n</tool_response>' }}
{%- if loop.last or (messages[loop.index0 + 1].role != "tool") %}
{{- '<|im_end|>\n' }}
{%- endif %}
{%- endif %}
{%- endfor %}
{%- if add_generation_prompt %}
{{- '<|im_start|>assistant\n' }}
{%- endif %}
那么接下来启动,把模板丢给vllm
bash
vllm serve 模型名称 --chat-template ./path-to-chat-template.jinja
注意:如果你使用修改后的模板启动,然后用open-webui做前端页面转发,修改后的模板不生效。这是因为open-webui每次都会覆盖原模型的模板,使用自己的模板,适合自己用。
启动open-webui
bash
source activate openwebui
export HF_ENDPOINT=https://hf-mirror.com
export ENABLE_OLLAMA_API=False #关闭调用ollama的api,使用vllm的
export OPENAI_API_BASE_URL=http://localhost:8000/v1 #vllm端口
open-webui sever
端口映射