当前位置: 首页 > java >正文

大模型的开发应用(十一):对话风格微调项目(下):微调与部署

这里写目录标题

  • 0 前言
  • 1 模型微调
    • 1.1 找到配置文件
    • 1.2 修改配置文件
      • PART 1
      • PART 2
      • PART 3
      • 使用Tensorboard实现训练可视化
    • 1.3 微调训练
    • 1.4 训练的停止条件
    • 1.5 模型转化、测试与合并
      • 模型的转化
      • 模型的测试
      • 模型合并
  • 2 推理部署
    • 2.1 对话模板对齐(本节有bug,具体看第2.4节)
    • 2.2 启动 LMDeploy 推理服务
    • 2.3 使用Streamlit演示
    • 2.4 修复 LMDeploy 中的对话模板Bug

0 前言

上篇文章,我们完成了数据集的制作,得到了一个拥有近两万条样本的数据集,随后进行了模型选型,筛选出了 Qwen2.5-1.5B-Instruct 作为我们的基座模型,这篇文章,我们来完成剩下的工作,包括模型的微调与部署。

1 模型微调

1.1 找到配置文件

本项目使用的微调框架为 Xtuner,目前Xtuner只能支持Qwen1.5,但是没关系,我们可以把Qwen2.5的模型路径给配上。

先找到 qwen1_5_1_8b_chat 的配置文件,本项目使用 QLoRA 微调,所以我们这里用 qwen1_5_1_8b_chat_qlora_alpaca_e3.py,位置如下图所示:
在这里插入图片描述

注意,这里必须是 Chat 模型的配置文件,我一次找的是 qwen1_5_1_8b 不带 chat 的,结果因为对话模板问题导致评估的时候生成的东西怪怪的。

先把配置文件复制一份到 /data/coding/utils/xtuner 目录下,然后改名为 qwen2_5_chat_dialog_style.py,接下来我们修改这个配置文件。

1.2 修改配置文件

下面的参数我是根据每张卡 12G 显存的GPU来配置的,如果显存不一样,只需要调整 batch_size 和 max_length 即可。

PART 1

这一部分要改的东西是最多的,包括模型路径(pretrained_model_name_or_path)、数据集路径(data_files)、输入样本最大长度(max_length)、批次大小(batch_size)、训练的最大伦次(max_epochs)、保存的检查点数量(save_total_limit),还有用于主观评估的问题(evaluation_inputs),要修改的地方,我已经在下面的程序片段中注释出来了(evaluation_inputs没有注释出来)。

#######################################################################
#                          PART 1  Settings                           #
#######################################################################
# Model
pretrained_model_name_or_path = "/data/coding/model_weights/Qwen/Qwen2.5-1.5B-Instruct"		# 修改
use_varlen_attn = False# Data
# alpaca_en_path = "tatsu-lab/alpaca"														# 修改(注释掉)
data_files = "/data/coding/EmotionalDialogue/convert_data.json"								# 修改
prompt_template = PROMPT_TEMPLATE.qwen_chat
max_length = 256																			# 修改
pack_to_max_length = True# parallel
sequence_parallel_size = 1# Scheduler & Optimizer
batch_size = 8  # per_device																# 修改
accumulative_counts = 16
accumulative_counts *= sequence_parallel_size
dataloader_num_workers = 0
max_epochs = 1000																			# 修改
optim_type = AdamW
lr = 2e-4
betas = (0.9, 0.999)
weight_decay = 0
max_norm = 1  # grad clip
warmup_ratio = 0.03# Save
save_steps = 500
save_total_limit = 5  # Maximum checkpoints to keep (-1 means unlimited)					# 修改# Evaluate the generation performance during the training
evaluation_freq = 500
SYSTEM = SYSTEM_TEMPLATE.alpaca
evaluation_inputs = ["闺蜜把我秘密当谈资,该不该撕破脸?", "老妈非让我嫁给她同事儿子,怎么逃啊!","男朋友给女主播刷火箭,算精神出轨吗?", "室友半夜和对象视频娇喘,怎么提醒?", "亲戚说我不生孩子就是自私,好想掀桌!", "领导周末发60秒语音矩阵,装没看见行吗?","被同事追问有没有整容,怎么优雅翻白眼?", "相亲对象第一次见面就想搂肩,油腻!","暗恋的人突然问我喜欢什么类型!", "针灸减肥被扎成仙人掌,一斤没掉!"]

这里用于主观评估的问题,至少要有5个以上,我们本次微调用的数据达到了万这个级别了,所以我这里选了10个,挑的方式很随机。如果是其他项目的话,用于主观评估的问题,需要有代表性,必须覆盖所有的目标场景。

PART 2

这部分只有一个 LoRA 的缩放系数改一下,一般情况下,lora_alpha 是秩的两倍,这是前人总结出来的经验,其他参数用默认。

#######################################################################
#                      PART 2  Model & Tokenizer                      #
#######################################################################
tokenizer = dict(type=AutoTokenizer.from_pretrained,pretrained_model_name_or_path=pretrained_model_name_or_path,trust_remote_code=True,padding_side="right",
)model = dict(type=SupervisedFinetune,use_varlen_attn=use_varlen_attn,llm=dict(type=AutoModelForCausalLM.from_pretrained,pretrained_model_name_or_path=pretrained_model_name_or_path,trust_remote_code=True,torch_dtype=torch.float16,quantization_config=dict(type=BitsAndBytesConfig,load_in_4bit=True,load_in_8bit=False,llm_int8_threshold=6.0,llm_int8_has_fp16_weight=False,bnb_4bit_compute_dtype=torch.float16,bnb_4bit_use_double_quant=True,bnb_4bit_quant_type="nf4",),),lora=dict(type=LoraConfig,r=64,lora_alpha=128,							# 修改lora_dropout=0.1,bias="none",task_type="CAUSAL_LM",),
)

PART 3

这部分主要改一下数据集相关的配置,就两个地方:dataset 和 dataset_map_fn,其他用默认:

#######################################################################
#                      PART 3  Dataset & Dataloader                   #
#######################################################################
alpaca_en = dict(type=process_hf_dataset,# dataset=dict(type=load_dataset, path=alpaca_en_path),					# 修改dataset=dict(type=load_dataset, path="json",data_files=data_files),		# 修改tokenizer=tokenizer,max_length=max_length,# dataset_map_fn=alpaca_map_fn,											# 修改dataset_map_fn=None,													# 修改template_map_fn=dict(type=template_map_fn_factory, template=prompt_template),remove_unused_columns=True,shuffle_before_pack=True,pack_to_max_length=pack_to_max_length,use_varlen_attn=use_varlen_attn,
)sampler = SequenceParallelSampler if sequence_parallel_size > 1 else DefaultSamplertrain_dataloader = dict(batch_size=batch_size,num_workers=dataloader_num_workers,dataset=alpaca_en,sampler=dict(type=sampler, shuffle=True),collate_fn=dict(type=default_collate_fn, use_varlen_attn=use_varlen_attn),
)

使用Tensorboard实现训练可视化

在配置文件中,搜索 set visualizer ,然后按照如下方式修改:

# set visualizer
from mmengine.visualization import Visualizer, TensorboardVisBackend
visualizer = dict(type=Visualizer, vis_backends=[dict(type=TensorboardVisBackend)])

至此,配置文件修改完毕。

1.3 微调训练

如果选择单卡微调,那么命令为:

xtuner train qwen2_5_chat_dialog_style.py

显存占用情况如下:
在这里插入图片描述

如果选择两张卡分布式微调,那么命令为:

NPROC_PER_NODE=2 xtuner train qwen2_5_chat_dialog_style.py --deepspeed deepspeed_zero2

因为我没跑,所以我也不知道显存占用情况怎么样,如果出现显存不足,那就把 --deepspeed 改成 deepspeed_zero2_offloaddeepspeed_zero3,也可以调小 batch_sizemax_length

损失函数下降情况可以利用 tensorboard 查看,新开一个终端,输入以下命令:

tensorboard --logdir /data/coding/utils/xtuner/work_dirs/qwen2_5_chat_dialog_style/20250614_193605/vis_data

然后在指定窗口(http://localhost:6006/,远程服务器需要SSH连接或者端口转发)查看。

在这里插入图片描述

1.4 训练的停止条件

模型每训练完 500 个step,就会评估一次,下面截图是训练了3500个step后的评估结果:
在这里插入图片描述
关于训练什么时候停止,关键需要看这十个主观评估的问题是否都达到了预期的效果,一个都不能少。每达到预期效果,则说明没收敛,当然,即便这十个问题都达到了预期效果,也不能说模型收敛了,因为主观评估的问题太少,有一定的偶然性。不过大模型训练到收敛是有难度的,而且GPU算力资源有限,因此没必要训练到收敛,只要主观评估的结果,连续若干次都能达到预期,训练就可以停止了。

查看主观评估的结果,不需要去翻控制台的打印信息,也不需要去看日志,在工作目录下的 vis_data 目录中:
在这里插入图片描述

我训练了18000个step,大概相当于31个epoch,花了超过12小时,发现十个主观评估问题都达到要求后,就停止训练了。下面是损失函数下降情况:
在这里插入图片描述

停止的时候,损失降到了0.2以下。

1.5 模型转化、测试与合并

模型的转化

我们使用模型最后一个检查点,因为Xtuner保存的检查点是低秩适配器,而且是以 pth 的文件保存,现在要将其转成 Hugging Face 模型,使用以下命令:

xtuner convert pth_to_hf qwen2_5_chat_dialog_style.py /data/coding/utils/xtuner/work_dirs/qwen2_5_chat_dialog_style/iter_18000.pth /data/coding/utils/xtuner/adapter_save_dir/qwen2_5/

转化成功后,控制台会显示 All done,同时在我们的指定路径下有会保存的 hugging face 模型。
在这里插入图片描述

模型的测试

我们先来和模型对话看看效果怎么样,在终端输入:

xtuner chat /data/coding/model_weights/Qwen/Qwen2.5-1.5B-Instruct --adapter /data/coding/utils/xtuner/adapter_save_dir/qwen2_5 --prompt-template qwen_chat --system-template alpaca

下面是测试情况:
在这里插入图片描述
从上面的回复可以看到,模型的回答风格达到了我们的预期,说明模型达到想要的效果。我们还可以把十个主观评估的问题依次输入,或者输入其他可用于主观评估的问题。

刚刚输入到终端的命令有两个模板, --prompt-template 是对话模板,--system-template 是系统提示词模板。因为我们在配置文件中,有 prompt_template = PROMPT_TEMPLATE.qwen_chatSYSTEM = SYSTEM_TEMPLATE.alpaca 这两句话,所以指定对话模板为 qwen_chat,系统提示词模板为 alpaca。如果想查看有哪些模板,可以在终端输入 xtuner chat -h

在这里插入图片描述

模型合并

终端输入以下命令:

xtuner convert merge /data/coding/model_weights/Qwen/Qwen2.5-1.5B-Instruct /data/coding/utils/xtuner/adapter_save_dir/qwen2_5/ /data/coding/EmotionalDialogue/model_weights/Qwen2.5-1.5B-Dialog-Style/

如果看到控制台输出 All done,说明合并成功。
在这里插入图片描述

2 推理部署

2.1 对话模板对齐(本节有bug,具体看第2.4节)

我们在训练的时候,模板用的是 Xtuner 定义的 qwen_chat 和 alpaca,我们可以在 xtuner/xtuner/utils/templates.py 中找到它们的详细定义:
在这里插入图片描述
在这里插入图片描述

实际上,Qwen1、Qwen1.5、Qwen2.5 自带的对话模板是不一样的,但是训练框架不会管这么多,它训练的时候用的都是 qwen_chat 和 alpaca,我们在推理部署的时候,需要使用训练时的对话模板。

常见的推理框架,如 vLLM 和 LMDeploy 都是支持自己指定对话模板的,vLLM 需要把对话模板转化为 jinjia2 格式,LMDeploy 则需要转为 json 格式。本项目用的推理框架是 LMDeploy,在 LMDeploy 的官方文档中,指明了对话模板的组织样式:

{"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",											# 能力,一般都是固定值 chat"stop_words": ["<|im_end|>"]									# 停止符
}

输入到模型中的信息,会被拼接成如下格式:

# user_content 是用户输入的内容,assistant_content 是模型答复
{system}{meta_instruction}{eosys}{user}{user_content}{eoh}{assistant}{assistant_content}{eoa}{separator}{user}...

下面是我根据 Xtuner 中的 qwen_chat 和 alpaca 构建的对话模板:

{"model_name": "Qwen2_5 Dialog Style","system": "<|im_start|>system\n","meta_instruction": "Below is an instruction that describes a task. Write a response that appropriately completes the request.\n","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|>", "<|endoftext|>"]
}

将上述内容保存为 dialog_style_chat_template.json。这里,separatorstop_words 在 qwen_chat 中有,capability 固定为 chateoa 可以从之前微调的时候的主观评估输出中找,它其实也是 qwen_chat 中的 SUFFIX

在这里插入图片描述

我们测试一下自定义的对话模板

lmdeploy chat /data/coding/EmotionalDialogue/model_weights/Qwen2.5-1.5B-Dialog-Style --chat-template /data/coding/utils/xtuner/dialog_style_chat_template.json

结果如下:
在这里插入图片描述
可以看到,输出的信息和我们设定的模板一致,第二轮因为涉及多轮对话,所以第二轮没有系统提示词,模型回复的后缀(<|im_end|>)会被后台程序自动去掉,这个不重要。

我们可以对比一下原始模型,在控制台输入:

lmdeploy chat /data/coding/model_weights/Qwen/Qwen2.5-1.5B-Instruct

使用原始模型时,不需要指定对话模板,lmdeploy 会去模型的权重文件目录中找自带的对话模板,即 Qwen2.5 官方推出的对话模板。下面是原始模型的问答记录:

在这里插入图片描述
可以看到,微调后的模型与微调前,答复的风格有了明显不同。

2.2 启动 LMDeploy 推理服务

启动推理比较简单,只需要一行命令启动服务:

lmdeploy serve api_server /data/coding/EmotionalDialogue/model_weights/Qwen2.5-1.5B-Dialog-Style --chat-template /data/coding/utils/xtuner/dialog_style_chat_template.json --model-name dialog_style

如果控制台出现下面的信息,说明推理的服务已经启动:
在这里插入图片描述

如果想使用 KV Cache 量化,可以指定 --quant-policy,即

lmdeploy serve api_server /data/coding/EmotionalDialogue/model_weights/Qwen2.5-1.5B-Dialog-Style --chat-template /data/coding/utils/xtuner/dialog_style_chat_template.json --model-name dialog_style --quant-policy 8

2.3 使用Streamlit演示

新建一个名为 demo_lmdeploy_streamlit.py 的文件,内容如下:

import streamlit as st
from openai import OpenAI# 合并对话历史
def build_messages(prompt, history):if history:messages = history.copy()else:# 若 history 为 None,则说明是单轮对话messages = []user_message = {"role": "user", "content": prompt}messages.append(user_message)return messages# 初始化客户端
@st.cache_resource
def get_client():# 如果没有 @st.cache_resource,那么每次在前端界面输入信息时,程序就会再次执行,导致模型重复导入client = OpenAI(base_url="http://0.0.0.0:23333/v1/",api_key="suibianxie")return client# 创建一个标题和一个副标题
st.title("💬 Style Chatbot")
st.caption("🚀 A streamlit chatbot powered by Self-LLM")# 如果session_state中没有"messages",则创建一个包含默认消息的列表
if "messages" not in st.session_state:st.session_state["messages"] = []# 遍历session_state中的所有消息,并显示在聊天界面上
for msg in st.session_state.messages:st.chat_message(msg["role"]).write(msg["content"])# 初始化客户端
client = get_client()# 如果用户在聊天输入框中输入了内容,则执行以下操作
if prompt := st.chat_input():# 在聊天界面上显示用户的输入st.chat_message("user").write(prompt)# 将当前提示词添加到消息列表,由于只考虑单轮,因此 history 置为 Nonemessages = build_messages(prompt=prompt, history=None)#调用模型chat_complition = client.chat.completions.create(messages=messages,model="dialog_style")#获取回答model_response = chat_complition.choices[0]response_text = model_response.message.content# 将用户问题和模型的输出添加到session_state中的messages列表中st.session_state.messages.append({"role": "user", "content": prompt})st.session_state.messages.append({"role": "assistant", "content": response_text})# 在聊天界面上显示模型的输出st.chat_message("assistant").write(response_text)

在终端中运行以下命令,启动streamlit服务,并将端口映射到本地,然后在浏览器中打开链接 http://localhost:6006/ ,即可看到聊天界面。

streamlit run demo_lmdeploy_streamlit.py --server.address 127.0.0.1 --server.port 6006

界面如下:

在这里插入图片描述

可以用相同的输入多试几次,如果每次生成的答案都一样,则说明过拟合。因为大模型在生成答案的时候,是通过softmax对词表中各个单词计算概率,如果多次输入同一个问题,而每次答案都一样(假设生成的序列为“你好幸苦呀”),说明在生成第一个 token 的时候,“你” 这个字的概率远远高于其他字,在生成第二个 token 的时候,“好”这个字的概率远远高于其他字,后面的序列都是如此,最后导致生成的答案几乎没有了随机性。

我们训练用的数据接近两万条,但只有一千个问题,也就是说,每个问题对应20种回答,所以本项目不太可能出现过拟合,因为在生成每个 token 的时候,在多个方向都有一定的概率,使得模型的回复有一定的随机性。

尝试如下:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.4 修复 LMDeploy 中的对话模板Bug

经过反复盘查,发现是对话模板出了问题,LMDeploy 的官方文档给的对话模板格式有问题,可能是版本比较老的官方文档。下面是我最终的对话模板:

{"meta_instruction": "You are a helpful assistant.","capability": "chat","eosys": "<|im_end|>\n","eoh": "<|im_end|>\n","system": "<|im_start|>system\n{{ system }}<|im_end|>\n","user": "<|im_start|>user\n{{ input }}<|im_end|>","assistant": "<|im_start|>assistant\n","eoa": "<|im_end|>","separator": "\n","stop_words": ["<|im_end|>","<|endoftext|>"]
}

上面的对话模板,除了meta_instruction之外,其他一个字符都不能改。因为我尝试了很多该法,比如增加 model_name 字段、去掉 eosyseoh 字段,user 字段的换行符去掉等,每修改一次,进行300次单轮对话实验(即重启 LMDeploy 推理服务,然后运行下面的统计代码),结果都会出现问题,包括但不限于:生成内容不带风格名称、生成内容全为温柔风格等。唯一改了基本没影响的,只有把 meta_instruction 字段改成:Below is an instruction that describes a task. Write a response that appropriately completes the request.\n

下面是统计的代码:

from openai import OpenAI
from tqdm import tqdm# 初始化客户端
client = OpenAI(base_url="http://0.0.0.0:23333/v1/",api_key="suibianxie")# 提示词
prompt = "相亲对象第一次见面就想搂肩,油腻!"
messages=[{"role":"user","content": prompt}]# 统计信息初始化
style_counts = {"温柔":0, "毒舌":0, "温柔开头的其他回答":0, "毒舌开头的其他回答":0, "不带风格名称":0}# 获取异常回复
abnormal_answer = []for i in tqdm(range(300)):# 调用模型try:chat_completion = client.chat.completions.create(messages=messages,model="/data/coding/EmotionalDialogue/model_weights/Qwen2.5-1.5B-Dialog-Style")except:chat_completion = client.chat.completions.create(messages=messages,model="dialog_style")# 获取输出内容model_reply = chat_completion.choices[0].message.content# 统计输出结果key = model_reply.split('\n')[0]if key == "温柔" or key == "毒舌":style_counts[key] += 1else:if key[:2] == "温柔":style_counts["温柔开头的其他回答"] += 1elif key[:2] == "毒舌":style_counts["毒舌开头的其他回答"] += 1else:style_counts["不带风格名称"] += 1line = str(i) + ' ' + keyabnormal_answer.append(line)# 异常信息写到 txt 文件中
with open("abnormal_answer.txt", "w", encoding="utf-8") as f:for item in abnormal_answer:f.write(item + "\n")  # 每个字符串单独一行print(style_counts)

输出:

{'温柔': 206, '毒舌': 94, '温柔开头的其他回答': 0, '毒舌开头的其他回答': 0, '不带风格名称': 0}

假如我不另外指定对话模板,此时 LMDeploy 会去模型的权重目录中找自带的对话模板,我再统计一次,结果为:

{'温柔': 26, '毒舌': 274, '温柔开头的其他回答': 0, '毒舌开头的其他回答': 0, '不带风格名称': 0}

我们训练用的数据集,两种风格的比例是1:1,上面的两次统计结果均严重偏离这个比例,原因我也不知道。总之,LMDeploy 的对话模板坑比较多,而且特别玄学。

Qwen 模型的对话模板现在是解决了,那以后要是微调其他模型,推理的时候怎么改对话模板?我找到了一份转换代码,下面介绍如何使用。

假设我们现在要微调 Chat-GLM3,先找到 Xtuner 中相关的对话模板:

在这里插入图片描述

我们把 Xtuner 中的对话模板复制,然后粘贴到下面 train_chat 后面:

import re
import json
from typing import Dict, Anydef universal_converter(original_template: Dict[str, Any]) -> Dict[str, Any]:"""将多种风格的原始模板转换为lmdeploy官方格式"""# 字段映射关系(核心逻辑)field_mapping = {# 基础字段映射"SYSTEM": "system","INSTRUCTION": ("user", "assistant"),  # 需要拆分处理"SUFFIX": "eoa","SEP": "separator","STOP_WORDS": "stop_words",# 特殊处理字段"SUFFIX_AS_EOS": None,  # 该字段在官方模板中不需要}# 初始化目标模板(包含必填字段默认值)converted = {"meta_instruction": "You are a helpful assistant.",  # 必填项"capability": "chat",  # 必填项"eosys": "<|im_end|>\n",  # 通常固定格式"eoh": "<|im_end|>\n",  # 通常固定格式}# 自动处理字段映射for src_key, dest_key in field_mapping.items():if src_key in original_template:value = original_template[src_key]# 处理需要拆分的字段(如INSTRUCTION)if isinstance(dest_key, tuple) and src_key == "INSTRUCTION":# 使用正则拆分user和assistant部分parts = re.split(r'(<\|im_start\|>assistant\n?)', value)converted["user"] = parts[0].strip()if len(parts) > 1:converted["assistant"] = parts[1] + parts[2] if len(parts) > 2 else parts[1]# 处理直接映射字段elif dest_key and not isinstance(dest_key, tuple):converted[dest_key] = value# 特殊处理system字段的占位符if "system" in converted:converted["system"] = converted["system"].replace("{system}", "{{ system }}")# 处理用户输入占位符if "user" in converted:converted["user"] = converted["user"].replace("{input}", "{{ input }}")# 自动处理停止词(兼容列表和字符串)if "stop_words" in converted and isinstance(converted["stop_words"], str):converted["stop_words"] = [converted["stop_words"]]# 保留原始模板中的额外字段(带警告)for key in original_template:if key not in field_mapping:print(f"Warning: 发现未映射字段 [{key}],已保留原样")converted[key] = original_template[key]return converted# 示例用法## qwen_chat 
# train_chat = dict(
#     SYSTEM=("<|im_start|>system\n{system}<|im_end|>\n"),
#     INSTRUCTION=("<|im_start|>user\n{input}<|im_end|>\n" "<|im_start|>assistant\n"),
#     SUFFIX="<|im_end|>",
#     SUFFIX_AS_EOS=True,
#     SEP="\n",
#     STOP_WORDS=["<|im_end|>", "<|endoftext|>"]
# )## chat
train_chat = dict(SYSTEM="<|system|>\n{system}",INSTRUCTION="<|user|>\n{input}<|assistant|>\n",SEP="\n",)# 执行转换
converted_template = universal_converter(train_chat)# 生成JSON文件
with open('chat_template.json', 'w') as f:json.dump(converted_template, f,indent=2,ensure_ascii=False,separators=(',', ': '))

运行成功后,用于 LMDeploy 的对话模板将保存在 chat_template.json 中。

如果这种方式不管用,那就不指定对话模板了,让 LMDeploy 去模型的权重目录中找自带的对话模板。

http://www.xdnf.cn/news/14401.html

相关文章:

  • 《AI辅助编程:从零掌握核心逻辑》工作坊开业
  • mysql修改密码笔记
  • 基于51单片机的智能小车:按键调速、障碍跟踪、红外循迹与数码管显示(一个合格的单片机课设)
  • 浙江康冠锁业携智能锁具亮相2025上海国际快递物流展
  • 山东大学软件学院创新项目实训开发日志——第十七周(二)
  • 【C语言扩展识别实数负数】2022-5-29
  • Web第二次方向考核复盘
  • OpenHarmony 5.0读取文件并写入到另一份文件(公共文件夹),并保持原先的格式以及编码类型
  • 论文略读:Does Refusal Training in LLMs Generalize to the Past Tense?
  • Hierarchical Vector Quantization for Unsupervised Action Segmentation
  • 介质访问控制——随机访问控制
  • Java的DI依赖注入
  • 2025如何快速给人物模型添加骨骼
  • 【Python机器学习(一)】NumPy/Pandas手搓决策树+使用Graphviz可视化(以西瓜书数据集为例)
  • 【深度剖析】领信卓越:福耀玻璃的数字化转型(上篇2:转型动机分析)
  • 嵌入式知识篇---三种坐标系
  • 揭开肾细胞的分子密码:当 METTL3 遇上 FOSL1【AbMole】
  • Android 与 ESP-01 WIFI模块通信
  • Tomcat 配置双击启动
  • 141. 环形链表
  • 概率期望DP
  • 【茶社茶楼专用软件】佳易王茶社茶楼计时计费会员管理软件介绍
  • 深度解析企业风控API技术实践:构建全方位企业风险画像系统
  • 【运维系列】【ubuntu22.04】安装Docker
  • 【性能优化】启用zram
  • 个人笔记-- TCL 替换
  • WebAssembly的本质与核心价值
  • 电磁场与电磁波篇---介质媒质导体
  • C++: 类 Class 的基础用法
  • 人工智能:神经网络原理、案例与 Python 代码