大模型微调项目实战(情绪对话模型-数据工程篇)

大模型的微调每个步骤都学完后,做一个实际的小项目来巩固学习内容,串联下知识点,学以致用。 大模型微调的用法目前只有2种:1是自我认知训练,2是对话风格。本次是2对话风格训练。

一、整体流程

  1. 需求分析
  2. 数据方案
  3. 模型选择
  4. 模型训练、评测
  5. 模型部署

二、需求分析

  • 确定任务类型:确定微调的具体目标,如文本分类、情感分析、问答系统等。明确项目的业务需求和预期效果。

三、数据方案

1、数据来源

a、甲方提供;

b、自己收集(成本较高,难度较大);

数据的获取方式:手动采集、爬虫、数据接口、AI生成

2、数据清洗、标注;

标注:自动化标注,人工处理;

3、指定数据集格式

一个问题对多个答案,或者多个问题对一个答案 。本项目我们选前者。

2、本项目的数据来源

 基于现有开源数据,让AI实现情绪数据制作;

安装一些依赖包:

# 智普AI接口
pip install zhipuai

# 加载Embeddingmodel
pip install sentence_transformers

# 魔塔社区
pip install modelscope

 具体的实现代码如下:《AI数据标注脚本.py》

import json
import time
import random
from zhipuai import ZhipuAI
from sentence_transformers import SentenceTransformer
import numpy as np

"""
示例数据:
# 用户输入库(可自定义扩展)
    user_inputs = [
        "今天心情不太好", "推荐个电影吧", "怎么才能早睡早起",
        "养猫好还是养狗好", "工作压力好大", "最近总是失眠"
    ]
"""
# 初始化模型
client = ZhipuAI(api_key="你自己的key")  # 替换为你的API Key
#加载Embeddingmodel
style_model = SentenceTransformer(r"D:\Test\LLMTrain\day15\embedding_model\zy\text2vec-base-chinese-sentence")

#===============================
#1.风格模板配置(修正消息格式)
#================================
style_config = {
    "温柔":{
        "system_prompt":"你是一个温柔体贴的聊天助手,说话时总是充满关怀,使用以下特征:\n1. 包含'呢、呀、啦'等语气词\n2. 使用🌸💖😊等温暖表情\n3. 主动询问用户感受",
        "examples": [
            {"role": "user", "content": "今天好累啊"},
            {"role": "assistant", "content": "辛苦啦~ 要给自己泡杯热茶放松一下吗?🌸"},
            {"role": "user", "content": "考试没考好..."},
            {"role": "assistant", "content": "没关系的呀~ 下次一定会更好!需要我陪你聊聊吗?😊"}
        ],
     "temperature": 0.3
    },
    "毒舌":{
        "system_prompt":"你是一个喜欢用犀利吐槽表达关心的朋友,需满足:\n1. 使用网络流行语(如'栓Q''退退退')\n2. 包含夸张比喻('你这速度堪比树懒')\n3. 结尾隐藏关心",
        "examples": [
            {"role": "user", "content": "又胖了5斤!"},
            {"role": "assistant", "content": "好家伙!你这是要把体重秤压成分子料理?🏋️"},
            {"role": "user", "content": "游戏又输了"},
            {"role": "assistant", "content": "菜就多练练!需要给你推荐《从零开始的电竞之路》吗?🎮"}
        ],
     "temperature": 0.7
    },
}

#========================
#生成函数(修正消息的结构)
#========================

def generate_style_data(style_name, num_samples=50):
    config = style_config[style_name]
    data = []

    # 构建消息上下文(包含系统提示和示例对话)
    messages = [
        {"role": "system", "content": config["system_prompt"]},
        *config["examples"]  # 直接展开示例对话
    ]

    # 用户输入库(可自定义扩展)
    user_inputs = [
        "今天心情不太好", "推荐个电影吧", "怎么才能早睡早起",
        "养猫好还是养狗好", "工作压力好大", "最近总是失眠"
    ]

    for _ in range(num_samples):
        try:
            # 随机选择用户输入
            user_msg = random.choice(user_inputs)

            # 添加当前用户消息
            current_messages = messages + [
                {"role": "user", "content": user_msg}
            ]

            # 调用API(修正模型名称)
            response = client.chat.completions.create(
                model="glm-3-turbo",
                messages=current_messages,
                temperature=config["temperature"],
                max_tokens=100
            )

            # 获取回复内容(修正访问路径)
            reply = response.choices[0].message.content

            # 质量过滤(数据审核)
            if is_valid_reply(style_name, user_msg, reply):
                data.append({
                    "user": user_msg,
                    "assistant": reply,
                    "style": style_name
                })

            time.sleep(1.5)  # 频率限制保护

        except Exception as e:
            print(f"生成失败:{str(e)}")

    return data

def is_valid_reply(style, user_msg, reply):
    """质量过滤规则(添加空值检查)"""
    # 基础检查
    if not reply or len(reply.strip()) == 0:
        return False

    # 规则1:回复长度检查
    if len(reply) < 5 or len(reply) > 150:
        return False

    # 规则2:风格关键词检查
    style_keywords = {
        "温柔": ["呢", "呀", "😊", "🌸"],
        "毒舌": ["好家伙", "栓Q", "!", "🏋️"]
    }
    if not any(kw in reply for kw in style_keywords.get(style, [])):
        return False

    # 规则3:语义相似度检查
    try:
        ref_text = next(msg["content"] for msg in style_config[style]["examples"]
                        if msg["role"] == "assistant")
        ref_vec = style_model.encode(ref_text)
        reply_vec = style_model.encode(reply)
        similarity = np.dot(ref_vec, reply_vec)
        return similarity > 0.65
    except:
        return False

#=============================
#3.执行生成(添加容错)
#============================
if __name__ == '__main__':
    all_data = []

    try:
        print("开始生成温柔风格数据...")
        gentle_data = generate_style_data("温柔", 50)
        all_data.extend(gentle_data)

        print("开始生成毒舌风格数据...")
        sarcastic_data = generate_style_data("毒舌", 50)
        all_data.extend(sarcastic_data)

    except KeyboardInterrupt:
        print("\n用户中断,保存已生成数据...")
    finally:
        with open("style_chat_data.json", "w", encoding="utf-8") as f:
            json.dump(all_data, f, ensure_ascii=False, indent=2)
        print(f"数据已保存,有效样本数:{len(all_data)}")

代码说明

(1)Embding模型 text2vec-base-chinese-sentence

用到“语义相似度检查”,使用Embding模型text2vec-base-chinese,需要从魔塔社区下载到本地。下载的代码:

# 魔塔社区地址:https://modelscope.cn/models/thomas/text2vec-base-chinese

#模型下载
from modelscope import snapshot_download
model_dir = snapshot_download('thomas\text2vec-base-chinese',cache_dir=r"D:\Test\LLMTrain\day15\embedding_model")

再进行归一化处理,《model_convert.py》

import numpy as np
from sentence_transformers import SentenceTransformer,models

model_path = r"D:\Test\LLMTrain\day15\embedding_model\thomas\text2vec-base-chinese"
bert = models.Transformer(model_path)
pooling = models.Pooling(bert.get_word_embedding_dimension(),
                        pooling_mode='mean')

# 添加缺失的归一化层
normalize = models.Normalize()

# 组合完整模型
full_model = SentenceTransformer(modules=[bert, pooling, normalize])
print(full_model)

save_path=r"D:\Test\LLMTrain\day15\embedding_model\zy\text2vec-base-chinese-sentence"
full_model.save(save_path)

# 加载修复后的模型
model = SentenceTransformer(r"D:\Test\LLMTrain\day15\embedding_model\zy\text2vec-base-chinese-sentence")

# 验证向量归一化
text = "测试文本"
vec = model.encode(text)
print("vec:", vec)  # 输出向量
print("修正后模长:", np.linalg.norm(vec))  # 应输出≈1.0


运行《AI数据标注脚本.py》生成的数据,确认下是否能够符合需求,若可行的话,项目就可以接着做,若不可行,则AI生成数据的方式行不通。

确定AI数据标注脚本的可行性后,还需要确定原始数据input_data 。

(2)生成的结果

结果会生成到 style_chat_data.json中,截取部分数据如下:

[
  {
    "user": "推荐个电影吧",
    "assistant": "当然可以呀!你想看什么类型的电影呢?是想要放松一下看看喜剧,还是想要沉浸在一个深刻的剧情里呢?🎬💖",
    "style": "温柔"
  },{
    "user": "最近总是失眠",
    "assistant": "哎呀,失眠真的很困扰人呢。是不是压力太大或者是有什么烦心的事情呢?我们可以一起想想办法缓解一下失眠的症状哦。💖",
    "style": "温柔"
  },{
    "user": "养猫好还是养狗好",
    "assistant": "这还用问吗?当然是养猫好啊!猫咪简直就是家里的小太阳,温柔、独立、还会自己上厕所,简直就是现代都市丽人的完美宠物!相比之下,狗狗就是个“四脚麻烦精”,天天黏人、破坏家具,还要定期遛弯,简直就是养了个“大型儿童”!栓Q了,我还是继续吸猫吧!😹",
    "style": "毒舌"
  }
】

3、开源的中文会话语料库

推荐大型清洁汉语会话语料库(LCCC),其中包含:LCCC-base 和 LCCC-large。为了保证语料库的质量,设计了严格的数据清洗流水线。该管道涉及一组规则和几个基于分类器的过滤器。诸如攻击性或敏感词、特殊符号、表情符号、语法错误的句子和不连贯的对话等噪音都会被过滤掉。

github地址:

https://github.com/thu-coai/CDial-GPT/tree/master

魔塔社区上下载:

https://modelscope.cn/datasets/OmniData/LCCC/files

该语料库里有很多数据,我们可以挑选1000条左右具有代表性的数据存入json里,再调整《AI数据标注脚本.py》,将json里的数据赋值给 user_inputs 变量即可。

四、大模型选型方案

1、大模型选型

(1)根据需求,需要大模型在中文的理解、对话方面有较强的能力。所以首选国内开源的大大模型,比如DeepSeek,QWen,ChatGLM等。综合比较能力强弱: DeepSeek > QWen >ChatGLM,但是DeepSeek太大,不便微调和部署,所以选择 QWen系列。

(2)QWen系列的NPL大模型有几种,结尾带-Instruct、 -Chat 等。带这2种后缀是基于基础模型, 进一步训练以增强其在具体应用场景中的表现,特别是对于需要理解并执行人类语言指令的任务。如果你的应用场景涉及到与用户交互或需要模型理解复杂的指令来完成特定任务,那么选择带有 -Instruct或-Chat 后缀的版本可能会更加合适。

综合考虑,我们先选择 Qwen/Qwen2.5-3B-Instruct 和Qwen/Qwen1.5-4B-Chat  。

2、客观评估

当前任务为日常聊天对话模型,主要要求模型的中文理解能力,因此这里以 CLUE (中文理解)数据进行 评测。在安装了opencompass的环境中执行命令查询CLUE相关数据集。
(opencompass) /project/opencompass# python tools/list_configs.py clue

# 结果有删减
+-----------------------------+------------------------------------------------------------------------------+
| Dataset                     | Config Path                                                                  |
|-----------------------------+------------------------------------------------------------------------------|
| CLUE_C3_gen                 | opencompass/configs/datasets/CLUE_C3/CLUE_C3_gen.py                          |
| CLUE_C3_gen_8c358f          | opencompass/configs/datasets/CLUE_C3/CLUE_C3_gen_8c358f.py                   |
| CLUE_C3_ppl                 | opencompass/configs/datasets/CLUE_C3/CLUE_C3_ppl.py                          |
| CLUE_ocnli_gen_51e956       | opencompass/configs/datasets/CLUE_ocnli/CLUE_ocnli_gen_51e956.py             |
| FewCLUE_bustm_gen           | opencompass/configs/datasets/FewCLUE_bustm/FewCLUE_bustm_gen.py              |
| FewCLUE_bustm_gen_634f41    | opencompass/configs/datasets/FewCLUE_bustm/FewCLUE_bustm_gen_634f41.py       |
| FewCLUE_bustm_ppl           | opencompass/configs/datasets/FewCLUE_bustm/FewCLUE_bustm_ppl.py              |
| FewCLUE_bustm_ppl_4b16c0    | opencompass/configs/datasets/FewCLUE_bustm/FewCLUE_bustm_ppl_4b16c0.py       |
| FewCLUE_csl_gen_28b223      | opencompass/configs/datasets/FewCLUE_csl/FewCLUE_csl_gen_28b223.py           |
| FewCLUE_csl_gen_87f4a8      | opencompass/configs/datasets/FewCLUE_csl/FewCLUE_csl_gen_87f4a8.py           |
| FewCLUE_csl_ppl             | opencompass/configs/datasets/FewCLUE_csl/FewCLUE_csl_ppl.py                  |
| FewCLUE_csl_ppl_769f8d      | opencompass/configs/datasets/FewCLUE_csl/FewCLUE_csl_ppl_769f8d.py           |
| FewCLUE_csl_ppl_841b62      | opencompass/configs/datasets/FewCLUE_csl/FewCLUE_csl_ppl_841b62.py           |
| FewCLUE_ocnli_fc_gen        | opencompass/configs/datasets/FewCLUE_ocnli_fc/FewCLUE_ocnli_fc_gen.py        |
| FewCLUE_ocnli_fc_gen_f97a97 | opencompass/configs/datasets/FewCLUE_ocnli_fc/FewCLUE_ocnli_fc_gen_f97a97.py |
| FewCLUE_ocnli_fc_ppl        | opencompass/configs/datasets/FewCLUE_ocnli_fc/FewCLUE_ocnli_fc_ppl.py        |
| FewCLUE_tnews_gen           | opencompass/configs/datasets/FewCLUE_tnews/FewCLUE_tnews_gen.py              |
| FewCLUE_tnews_gen_b90e4a    | opencompass/configs/datasets/FewCLUE_tnews/FewCLUE_tnews_gen_b90e4a.py       |
| FewCLUE_tnews_ppl           | opencompass/configs/datasets/FewCLUE_tnews/FewCLUE_tnews_ppl.py              |
| FewCLUE_tnews_ppl_fff486    | opencompass/configs/datasets/FewCLUE_tnews/FewCLUE_tnews_ppl_fff486.py       |
+-----------------------------+------------------------------------------------------------------------------+
当前任务大多是短语对话,可以选择 FewCLUE_bustm_gen (短文本分类)、FewCLUE_ocnli_fc_gen(自然语言推理)对预期模型进行评估。
注意:若不清楚到底使用哪个数据集评估,可以将搜索:比如 (1)我想做一个法律类的大模型评判,开源的数据集有哪些?(2)我是做新闻分类的大模型评判,开源的数据集有哪些?
我们将上一步选择的那2个大模型和这2个数据集进行OpenCompass评测,使用“命令行”方式,步骤如下:
(1)将2个大模型下载到本地;
(2)调整这2个大模型在OpenCompass里的配置文件,主要调整path值为本地目录;
(3)执行评测命令:
python run.py \
--models hf_qwen2_5_3b_instruct hf_qwen1_5_4b_chat \
--datasets FewCLUE_bustm_gen FewCLUE_ocnli_fc_gen \
--debug

关于OpenCompass的具体使用方法请看这篇文章《大模型评估测试和OpenCompass模型评估框架》

评估的结果如下:

dataset        version    metric    mode      qwen2.5-3b-instruct-hf    qwen1.5-4b-chat-hf
-------------  ---------  --------  ------  ------------------------  --------------------
bustm-dev      5cc669     accuracy  gen                        63.75                 54.37
bustm-test     5cc669     accuracy  gen                        64.22                 53.61
ocnli_fc-dev   51e956     accuracy  gen                        50.62                 61.25
ocnli_fc-test  51e956     accuracy  gen                        46.47                 65.99

综合来看,qwen1.5-4b-chat的效果更好一些。

注意:

(1)gen(生成类)的数据集得分越高越好,ppl(困惑度)的数据集得分越低越好。

(2)对比大模型,一般分类 几种:

  • 相同大模型、相同版本,不同参数量大小;
  • 相同大模型、不同版本;
  • 不同大模型;

(3)模型参数量的大小如何选择?

  • 服务器的配置(成本预算);
  • 任务的复杂度(需求、特点);

五、大模型微调训练

1、大模型微调训练的框架选型

大模型微调训练的框架有 LLama Factory 和 XTuner 。

(1)微调过程中,我们的这个项目的结果偏向于主观的评测(对话风格没有标准答案)XTuner在微调过程中就提供了主观评测,所以推荐使用XTuner来微调训练。
(2)若偏向于客观(指标)评测,可以使用LLama Factory 。LLama Factory 在微调过程中,只能看到loss曲线数据,看不到主观的 评测数据。

2、制作XTuner微调的数据集

将《数据方案》中生成的对话数据集style_chat_data.json 转成XTuner支持的格式。无需写代码,直接让AI工具帮我们转。比如,使用ChatGPT的提示词:
你按照以下步骤转换一个json文件:
-读取附件里的json文件;
-转成一个新的json文件,新json文件的格式如下:
[{
    "conversation":[
        {
            "system": "xxx",
            "input": "xxx",
            "output": "xxx"
        }
    ]
}] ,
-源user节点对应input节点;
-assistant节点等于style节点\n再加上output节点;  
-源json中style节点若为“温柔”,则新json中system的值为“你是一个温柔体贴的聊天助手,说话时总是充满关怀,使用以下特征:\n1. 包含'呢、呀、啦'等语气词\n2. 使用🌸💖😊等温暖表情\n3. 主动询问用户感受” ;源json中style节点若为“毒舌”,则新json中system的值为“你是一个喜欢用犀利吐槽表达关心的朋友,需满足:\n1. 使用网络流行语(如'栓Q''退退退')\n2. 包含夸张比喻('你这速度堪比树懒')\n3. 结尾隐藏关心”;
-下载新的json文件

3、XTuner大模型微调

安装XTuner以及使用的具体方法请看这2篇文章。

轻量化大模型微调工具XTuner指令微调实战(上篇)

轻量化大模型微调工具XTuner指令微调实战(下篇)

3.1 修改配置文件


(1)大模型本地地址;data_files路径;
(2)max_length:216;
(2)batch_size:10;
(3)max_epochs:3000
(4)验证数据:从数据集中选至少5条,并且每条问题要有多种风格场景(温柔+毒舌),这样才更加便捷的判断微调的效果;
(5)qlora:改为8位的;
(6)lora-r:64,lora-alpha=128;(2倍关系)
(7)dataset:path=json


3.2 微调什么时候可以停止?(收敛)

在微调初期,需要观察loss的值一定要是下降趋势,观察期大概20分钟左右,至少走1个完整的步长。微调停止的校验有2条:
(1)几条验证数据必须要完全与预期的效果一致(与数据集基本一样)后才可以停止。
(2)loss的变化率很小,大概0.05左右就可以停止了。

实测情况:

(1)使用一张RTX 4090(24G)显卡,跑了1个小时多,loss从3.1下降到0.1左右,检查那几条验证数据,基本符合要求(拟合/收敛),就可以停下来了。我服务器配置如下:

(2)我们需要能够看懂执行的输出日志!

(3)验证对话中若发现,相同问题,问多次每次的答案都是一样的,则说明过拟合了。

3.3 微调训练、模型合并和转换

 执行微调训练的命令,显卡4090的服务器跑了6个小时左右。

nohup xtuner train  qwen1_5_4b_chat_qlora_alpaca_e3.py &

执行模型合并命令

xtuner convert pth_to_hf ./qwen1_5_4b_chat_qlora_alpaca_e3.py /root/autodl-tmp/project/xtuner/work_dirs/qwen1_5_4b_chat_qlora_alpaca_e3/iter_23500.pth  /root/autodl-tmp/llm/hf 

执行模型转换命令

xtuner convert merge  /root/autodl-tmp/llm/Qwen/Qwen1.5-4B-Chat /root/autodl-tmp/llm/hf   /root/autodl-tmp/llm/Qwen1.5-4B-Chat-hf

完成后,我们微调出来的大模型就成功诞生了。新的大模型保存的目录是 /root/autodl-tmp/llm/Qwen1.5-4B-Chat-hf  。

六、验证模型效果

微调训练好的大模型,需要使用推理框架验证其效果。推理框架一般有 LMDeploy、vLLM、SGLang、 Xinference等。

需要特别注意的是,微调训练的框架XTuner使用的对话模版与推理框架并不一致,因为在使用推理框架的时候,需要与XTuner对话模型对齐。

1、如何对齐对话模版?

一般我们会在推理框架运行微调后的大模型命令中指定对话模版,该对话模版就是从XTuner内置的对话模版转换来的。

2、XTuner对话模版转LMDeploy支持的json格式

思路:可以使用大模型来帮我们写实现代码,推荐使用DeepSeek来写代码。

LMDeploy的自定义对话模版的官方文档地址:

https://lmdeploy.readthedocs.io/zh-cn/latest/advance/chat_template.html

chat_tmpl_json_gen.py

import json

# 原始对话模板配置,根据自己选择的大模型,取xtuner/xtuner/utils/templates.py里找对应的内容
qwen_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|>"]
)

# 转换逻辑
converted = {
    "model_name": "qwen-chat",
    "system": qwen_chat["SYSTEM"].split("{system}")[0],          # 提取系统起始标记
    "meta_instruction": "You are a robot developed by LMDeploy.",# 默认系统指令
    "eosys": qwen_chat["SYSTEM"].split("{system}")[1].strip(),   # 提取系统结束标记
    "user": "<|im_start|>user\n",                               # 用户起始标记
    "eoh": "<|im_end|>\n",                                      # 用户结束标记
    "assistant": "<|im_start|>assistant\n",                     # 助理起始标记
    "eoa": qwen_chat["SUFFIX"],                                 # 助理结束标记
    "separator": qwen_chat["SEP"],
    "capability": "chat",
    "stop_words": [qwen_chat["STOP_WORDS"][0]]                  # 只保留第一个停止词
}

# 保存为JSON文件
with open("qwen_chat_template.json", "w", encoding="utf-8") as f:
    json.dump(converted, f, indent=4, ensure_ascii=False)

print("转换完成!生成的JSON文件已保存为 qwen_chat_template.json")

将打印出来的json字符串保存到一个json文件中。

LMDeploy运行大模型时指定该json文件

lmdeploy serve api_server /root/autodl-tmp/llm/Qwen1.5-4B-Chat-hf --chat-template ./qwen.json

3、XTuner对话模版转vLLM支持的jinja2格式

import json


def dict_to_jinja(template_dict):
    jinja_template = ""

    # Handle SYSTEM part
    if "SYSTEM" in template_dict:
        system_template = template_dict["SYSTEM"]
        jinja_template += (
            "{%- if tools %}\n"
            " {{- '" + system_template.split("{system}")[0] + "' }}\n"
            " {%- if messages[0]['role'] == 'system' %}\n"
            " {{- messages[0]['content'] }}\n"
            " {%- else %}\n"
            " {{- 'You are Qwen, created by Alibaba Cloud. You are a helpful assistant.' }}\n"
            " {%- endif %}\n"
            " {{- \"" + system_template.split("{system}")[1] + "\" }}\n"
            "{%- else %}\n"
            " {%- if messages[0]['role'] == 'system' %}\n"
            " {{- '" +
            system_template.split("{system}")[0] + "' + messages[0]['content'] + '" + system_template.split("{system}")[1] + "' }}\n"
            " {%- else %}\n"
            " {{- '" +
            system_template.split("{system}")[0] + "You are Qwen, created by Alibaba Cloud. You are a helpful assistant." +
            system_template.split("{system}")[1] + "' }}\n"
            " {%- endif %}\n"
            "{%- endif %}\n"
        )

    # Handle messages loop
    jinja_template += (
        "{%- for message in messages %}\n"
        " {%- if (message.role == \"user\") or (message.role == \"system\" and not loop.first) or (message.role == \"assistant\" and not message.tool_calls) %}\n"
        " {{- '<|im_start|>' + message.role + '\\n' + message.content + '<|im_end|>' + '\\n' }}\n"
        " {%- elif message.role == \"assistant\" %}\n"
        " {{- '<|im_start|>' + message.role }}\n"
        " {%- if message.content %}\n"
        " {{- '\\n' + message.content }}\n"
        " {%- endif %}\n"
        " {%- for tool_call in message.tool_calls %}\n"
        " {%- if tool_call.function is defined %}\n"
        " {%- set tool_call = tool_call.function %}\n"
        " {%- endif %}\n"
        " {{- '\\n<tool_call>\\n{\"name\": \"' }}\n"
        " {{- tool_call.name }}\n"
        " {{- '\", \"arguments\": ' }}\n"
        " {{- tool_call.arguments | tojson }}\n"
        " {{- '}\\n</tool_call>' }}\n"
        " {%- endfor %}\n"
        " {{- '<|im_end|>\\n' }}\n"
        " {%- elif message.role == \"tool\" %}\n"
        " {%- if (loop.index0 == 0) or (messages[loop.index0 - 1].role != \"tool\") %}\n"
        " {{- '<|im_start|>user' }}\n"
        " {%- endif %}\n"
        " {{- '\\n<tool_response>\\n' }}\n"
        " {{- message.content }}\n"
        " {{- '\\n</tool_response>' }}\n"
        " {%- if loop.last or (messages[loop.index0 + 1].role != \"tool\") %}\n"
        " {{- '<|im_end|>\\n' }}\n"
        " {%- endif %}\n"
        " {%- endif %}\n"
        "{%- endfor %}\n"
    )

    # Handle INSTRUCTION and SUFFIX
    if "INSTRUCTION" in template_dict and "SUFFIX" in template_dict:
        jinja_template += (
                "{%- if add_generation_prompt %}\n"
                " {{- '" + template_dict["INSTRUCTION"] + "' }}\n"
                                                          "{%- endif %}\n"
        )

    return jinja_template


# Example usage
template_dict = 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|>"]
)

jinja_result = dict_to_jinja(template_dict)
print(jinja_result)

将打印的内容保存到jinja格式的文件中。

vLLM运行大模型时候指定该jinja文件。

vllm serve /root/autodl-tmp/llm/Qwen1.5-4B-Chat-hf --chat-template ./qwen-chat-template.jinja

4、LMDeploy运行和验证微调后的大模型

第3步使用LMDeploy命令启动微调后的大模型后,写个代码调用大模型的对话功能。创建chat_to_llm.py文件。

#多轮对话
from openai import OpenAI
 
#定义多轮对话方法
def run_chat_session():
    #初始化客户端
    client = OpenAI(base_url="http://localhost:23333/v1/",api_key="token-abc123")
    #初始化对话历史
    #chat_history = []
    #启动对话循环
    while True:
        
        chat_history = []
        #获取用户输入
        user_input = input("用户:")
        if user_input.lower() == "exit":
            print("退出对话。")
            break
        #更新对话历史(添加用户输入)
        chat_history.append({"role":"user","content":user_input})
        #调用模型回答
        try:
            chat_complition = client.chat.completions.create(messages=chat_history,model="/root/autodl-tmp/llm/Qwen1.5-4B-Chat-hf")
            #获取最新回答
            model_response = chat_complition.choices[0]
            print("AI:",model_response.message.content)
            #更新对话历史(添加AI模型的回复)
            #chat_history.append({"role":"assistant","content":model_response.message.content})
        except Exception as e:
            print("发生错误:",e)
            break
if __name__ == '__main__':
    run_chat_session()

对话效果如下:

七、Streamlit搭建前端交互

1、Streamlit定义

Streamlit 是一个基于 Python 的开源 Web 应用程序框架,专为数据科学家和机器学习工程师设计,用于快速创建数据可视化应用。它的主要优势在于简单性和快速迭代,用户可以使用几行代码创建交互式应用,无需深入了解前端技术如 HTML、CSS 等。

2、Streamlit 特点


易于使用:Streamlit 的设计哲学是简洁性,它允许用户通过简单的 Python 脚本快速构建 Web 应用。
即时预览:Streamlit 应用在编写代码时可以实时预览,这使得开发过程更加高效。
强大的交互性:Streamlit 提供了多种交互组件,如按钮、滑块、文本输入等,使得创建交互式应用变得简单。
数据可视化:支持多种图表库,如 Matplotlib、Altair 等,方便进行数据可视化。
适用于快速原型设计:适合快速开发和展示数据分析结果,特别适用于个人或小团队。

3、编写代码

上面已经使用LMDeploy将微调后的大模型运行起来了。我们可以使用Streamlit搭建一个前端站点与该大模型进行交互。
pip install streamlit

chat_app.py

import streamlit as st
from openai import OpenAI

# 初始化客户端
client = OpenAI(base_url="http://localhost:23333/v1/", api_key="suibianxie")

# 设置页面标题
st.title("项目一效果演示")

# 初始化session状态(仅用于显示历史)
if "messages" not in st.session_state:
    st.session_state.messages = []

# 显示历史消息
for message in st.session_state.messages:
    with st.chat_message(message["role"]):
        st.markdown(message["content"])

# 获取用户输入
if prompt := st.chat_input("请输入您的问题,或输入exit退出"):
    # 处理退出命令
    if prompt.lower() == "exit":
        st.info("退出对话。")
        st.stop()
    
    # 添加用户消息到显示历史
    st.session_state.messages.append({"role": "user", "content": prompt})
    with st.chat_message("user"):
        st.markdown(prompt)

    try:
        # 发起API请求(每次只发送当前消息)
        response = client.chat.completions.create(
            messages=[{"role": "user", "content": prompt}],  # 每次只发送当前问题
            model="/root/autodl-tmp/llm/Qwen1.5-4B-Chat-hf"
        )
        
        # 获取模型回复
        model_response = response.choices[0].message.content
        
        # 添加AI回复到显示历史
        st.session_state.messages.append({"role": "assistant", "content": model_response})
        with st.chat_message("assistant"):
            st.markdown(model_response)

    except Exception as e:
        st.error(f"发生错误:{e}")
streamlist run chat_app.py
 当你运行上述命令后,Streamlit 会自动在默认的 Web 浏览器中打开应用页面。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值