【大模型微调】如何解决llamaFactory微调效果与vllm部署效果不一致如何解决

以下个人没整理太全

一、生成式语言模型的对话模板介绍

使用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聊天模板

  1. 作用:为了使语言模型支持聊天协议,vLLM要求模型在其tokenizer配置中包含聊天模板。

  2. 聊天模板定义 :聊天模板是一个Jinja2模板,用于指定角色、消息和其他特定于聊天的token如何在输入中编码。

    可以在 NousResearch/Meta-Llama-3-8B-Instruct 的示例聊天模板可以在这里找到。

  3. 使用场景

    • 有些模型即使经过指令/聊天微调,也不提供聊天模板。对于这些模型,可以在--chat-template参数中使用聊天模板的文件路径或字符串形式的模板手动指定其聊天模板。
    • 如果没有聊天模板,服务器将无法处理聊天,并且所有聊天请求都会出错。
  4. 使用方法

    bash 复制代码
    vllm serve <model> --chat-template ./path-to-chat-template.jinja
  5. 模板来源 :vLLM社区为流行的模型提供了一组聊天模板,可以在examples目录下找到它们。

二、LMDeploy自定义对话模板

  1. 添加对话模板形式

    • 利用现有对话模板:直接配置一个json文件使用。
    • 自定义Python对话模板类:注册成功后直接使用,优点是自定义程度高,可控性强。
  2. 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直接注册成新的对话模板。
  1. 模板拼接形式

    {system}{meta_instruction}{eosys}{user}{user_content}{eoh}{assistant}{assistant_content}{eoa}{separator}{user}...

  2. 使用方法

    • 使用CLI工具时,可以通过--chat-template传入自定义对话模板。

      bash 复制代码
      lmdeploy serve api_server internlm/internlm2_5-7b-chat --chat-template ${JSON_FILE}
    • 通过接口函数传入。

      python 复制代码
      from lmdeploy import ChatTemplateConfig, serve
      serve('internlm/internlm2_5-7b-chat', chat_template_config=ChatTemplateConfig.from_json('${JSON_FILE}'))
  3. 自定义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

端口映射

相关推荐
叶子2024226 分钟前
使用labelme进行实例分割标注
学习
zhuyixiangyyds15 分钟前
day28图像处理OpenCV
图像处理·笔记·学习
人工干智能15 分钟前
科普:如何通过ROC曲线,确定二分类的“理论阈值”
大数据·人工智能·分类
绝顶大聪明17 分钟前
图像预处理(OpenCV)-part2
人工智能·opencv·计算机视觉
xiao_yuzaijia23 分钟前
[文献阅读] chinese-roberta Pre-Training With Whole Word Masking for Chinese BERT
人工智能·深度学习·bert
江拥羡橙31 分钟前
2025年,HarmonyOS认证学习及考试
学习·华为·harmonyos·鸿蒙·华为证书·harmonyos认证·华为证书考试
Despacito0o40 分钟前
OpenCV图像增强实战教程:从理论到代码实现
人工智能·opencv·计算机视觉
东临碣石821 小时前
【字节跳动AI论文】Seaweed-7B:视频生成基础模型的高成本效益培训
人工智能
大话数据分析1 小时前
现在AI大模型能帮做数据分析吗?
人工智能·数据分析
lixy5791 小时前
深度学习之线性代数基础
人工智能·深度学习·线性代数