开源大模型及数据集:ModelScope 魔搭社区

ModelScope 魔搭社区是一个致力于加速 AI 开发的开源社区,它汇聚了众多行业前沿的模型、数据集和 AI 应用。在这里,你可以探索到丰富的 AI 资源,涵盖视觉检测与追踪、光学字符识别、逆向文本规范化、多模态表示、蛋白质结构生成、蛋白质功能预测等多个领域。ModelScope 提供了强大的计算基础设施,支持模型服务,让你能够轻松构建自己的模型和应用。

  • ModelScope Library:这是一款高效的 Python 库,专为模型推理、微调和评估而设计。它为各种 AI 模型(包括视觉、语音和大语言模型)提供了一个统一的接口,让你能够充分挖掘模型的潜力。

  • ModelHub:这是一个开源的 AI 模型和数据集托管中心,你可以在这里找到海量的资源,为你的项目提供强大的支持。

  • Popular Studio:ModelScope 的 Studio 为你提供了一个免费且灵活的 AI 应用展示空间。你可以基于 ModelScope 平台上的模型提供的原子能力,自主构建和演示不同的 AI 应用。

  • 框架与工具

    • Eval-Scope:这是一个简化大模型评估和性能基准测试的高效、可定制框架。

    • Swift:魔搭大模型训练推理工具箱,支持 LLaMA、千问、ChatGLM、BaiChuan 等多种模型,以及 LoRA 等多种训练方式。

    • ModelScope-Agent:连接 ModelScope 模型能力与万物的桥梁,将模型与现实世界紧密相连。


本地大模型微调(Qwen2-0.5B)

数据准备与处理

数据集概况

本次微调所用的数据集为“zh_cls_fudan-news”,包含训练集和测试集两个部分,原始数据以 JSONL 格式存储。每条数据记录由“text”(文本内容)、“category”(分类选项列表)和“output”(正确分类)三个字段组成。

数据转换

为了将原始数据集转换成适合大模型微调的格式,我编写了 dataset_jsonl_transfer 函数。该函数读取原始 JSONL 文件,将每条数据的“text”和“category”字段组合成“input”,与“output”字段一起,构建成新的 JSON 格式数据,并保存到新的 JSONL 文件中。这样处理后的数据集,每行都是一个包含“input”和“output”字段的 JSON 对象,方便后续的模型训练和预测使用。

数据预处理

在训练大模型之前,还需对数据集进行进一步的预处理。我定义了 process_func 函数,对每条数据执行以下操作:

  • 使用分词器对“input”和“output”进行分词,生成对应的 input_idsattention_mask

  • 为“input”部分添加特定的系统提示和用户提示,引导模型在生成文本时遵循既定的文本分类任务格式。

  • 将“input”和“output”的 input_idsattention_mask 拼接起来,并在末尾添加一个额外的 pad_token_id 和对应的 attention_mask,以标识序列的结束。

  • 生成 labels,用于模型训练时的监督学习。labels 的前半部分与“input”部分的 input_ids 对应,全部设置为 -100(表示这部分不参与损失计算),后半部分与“output”部分的 input_ids 对应,直接使用其 input_ids,并在末尾添加一个 pad_token_id

  • 对处理后的数据进行截断,确保序列长度不超过预设的最大长度 MAX_LENGTH(本例中为 384)。

模型配置与训练

加载预训练模型和分词器

从指定的模型目录 model_dir 中加载预训练的 Qwen2-0.5B 模型和对应的分词器。分词器使用 use_fast=False 参数,以确保分词结果的准确性;模型则使用 device_map=device 参数将其加载到 GPU 上(如果可用),并设置 torch_dtype=torch.bfloat16 以使用混合精度训练,提升训练效率。此外,调用 model.enable_input_require_grads() 方法,开启梯度检查点功能,以减少显存占用。

创建 LoRA 配置

为了在微调过程中引入 LoRA(Low-Rank Adaptation)技术,我创建了一个 LoraConfig 对象,配置了以下参数:

  • task_type=TaskType.CAUSAL_LM:指定任务类型为因果语言模型。

  • target_modules:列出需要应用 LoRA 的模型模块,包括 q_projk_projv_projo_projgate_projup_projdown_proj 等。

  • inference_mode=False:设置为训练模式。

  • r=8:LoRA 的秩,控制 LoRA 矩阵的大小。

  • lora_alpha=32:LoRA 的缩放因子,影响 LoRA 的学习速率。

  • lora_dropout=0.1:LoRA 层的 dropout 比例,用于正则化。

将 LoRA 应用于模型

通过调用 get_peft_model(model, config) 函数,将 LoRA 配置应用到预训练模型上,得到用于微调的 LoRA 模型。

创建微调参数

使用 TrainingArguments 类创建微调参数对象,配置了以下关键参数:

  • output_dir:指定输出目录,用于保存训练过程中的模型检查点和最终的微调模型。

  • per_device_train_batch_size=4:设置每个设备的训练批次大小为 4。

  • gradient_accumulation_steps=4:梯度累积步数为 4,用于模拟更大的批次大小,提高训练稳定性。

  • logging_steps=10:每 10 步记录一次训练日志。

  • num_train_epochs=2:训练轮次为 2。

  • save_steps=100:每 100 步保存一次模型检查点。

  • learning_rate=1e-4:学习率为 0.0001。

  • save_on_each_node=True:在每个节点上保存模型。

  • gradient_checkpointing=True:开启梯度检查点功能,进一步减少显存占用。

  • report_to="none":不将训练报告发送到外部平台。

训练模型

创建 Trainer 对象,传入 LoRA 模型、微调参数、训练数据集以及数据收集器 DataCollatorForSeq2Seq。此外,还添加了 SwanLabCallback 回调,用于在 SwanLab 平台上记录训练过程中的数据。调用 trainer.train() 方法,启动模型的微调训练过程。

微调可视化配置

我们使用SwanLab来监控整个训练过程,并评估最终的模型效果。如果是第一次使用 SwanLab,则需要注册 SwanLab 账号:https://swanlab.cn,注册成功之后,在用户设置页面复制API Key,在训练开始时需要用到。

在命令行使用swanlab login来进行登录

# Qwen2-0.5B-train.py

# 导入json模块,用于处理JSON格式的数据
import json
# 导入pandas模块,用于数据处理和分析
import pandas as pd
# 导入torch模块,PyTorch是一个开源的机器学习库,用于构建和训练深度学习模型
import torch
# 导入datasets模块中的Dataset类,用于处理和加载数据集
from datasets import Dataset
# 导入modelscope模块中的AutoTokenizer类,用于自动加载预训练模型的分词器
from modelscope import AutoTokenizer
# 导入swanlab.integration.transformers模块中的SwanLabCallback类,用于在训练过程中记录和回调数据
from swanlab.integration.transformers import SwanLabCallback
# 导入peft模块中的LoraConfig类、TaskType类和get_peft_model函数,用于配置和应用LoRA(Low-Rank Adaptation)微调技术
from peft import LoraConfig, TaskType, get_peft_model
# 导入transformers模块中的AutoModelForCausalLM类、TrainingArguments类、Trainer类和DataCollatorForSeq2Seq类
# AutoModelForCausalLM用于加载预训练的因果语言模型
# TrainingArguments用于配置训练参数
# Trainer用于管理模型的训练过程
# DataCollatorForSeq2Seq用于处理序列到序列任务的数据
from transformers import AutoModelForCausalLM, TrainingArguments, Trainer, DataCollatorForSeq2Seq
# 导入os模块,用于操作文件和目录
import os
# 导入swanlab模块,用于记录实验日志和结果
import swanlab

# 权重根目录,定义了模型权重和数据集存储的根目录
BASE_DIR = r'F:\Projects\Python\Model'

# 设备名称,根据是否有可用的CUDA设备选择使用GPU或CPU
device = 'cuda' if torch.cuda.is_available() else 'cpu'
# device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

# 数据集处理函数,包括:训练数据集和测试数据集
def dataset_jsonl_transfer(origin_path, new_path):
    """
    将原始数据集转换为大模型微调所需数据格式的新数据集
    """
    messages = []

    # 读取原JSONL文件
    with open(origin_path, "r", encoding="utf-8") as file:
        for line in file:
            # 解析每一行原始数据(每一行均是一个JSON格式)
            data = json.loads(line)
            text = data["text"]
            catagory = data["category"]
            output = data["output"]
            message = {
                "input": f"文本:{text},分类选项列表:{catagory}",
                "output": output,
            }
            messages.append(message)

    # 保存处理后的JSONL文件,每行也是一个JSON格式
    with open(new_path, "w", encoding="utf-8") as file:
        for message in messages:
            file.write(json.dumps(message, ensure_ascii=False) + "\n")


# 在使用数据集训练大模型之前,对每行数据进行预处理
def process_func(example):
    """
    将数据集进行预处理
    """
    MAX_LENGTH = 384  # 定义最大序列长度
    input_ids, attention_mask, labels = [], [], []
    # 构造模型输入的指令部分
    instruction = tokenizer(f"<|im_start|>system\n你是一个文本分类领域的专家,你会接收到一段文本和几个潜在的分类选项列表,请输出文本内容的正确分类<|im_end|>\n<|im_start|>user\n{example['input']}<|im_end|>\n<|im_start|>assistant\n", add_special_tokens=False)  # add_special_tokens 不在开头加 special_tokens
    # 构造模型输入的响应部分
    response = tokenizer(f"{example['output']}", add_special_tokens=False)
    # 将指令和响应的输入ID、注意力掩码和标签拼接起来
    input_ids = instruction["input_ids"] + response["input_ids"] + [tokenizer.pad_token_id]
    attention_mask = instruction["attention_mask"] + response["attention_mask"] + [1]  # 因为eos token咱们也是要关注的所以 补充为1
    labels = [-100] * len(instruction["input_ids"]) + response["input_ids"] + [tokenizer.pad_token_id]

    # 如果序列长度超过最大长度,则进行截断
    if len(input_ids) > MAX_LENGTH:
        input_ids = input_ids[:MAX_LENGTH]
        attention_mask = attention_mask[:MAX_LENGTH]
        labels = labels[:MAX_LENGTH]

    return {
        "input_ids": input_ids,
        "attention_mask": attention_mask,
        "labels": labels
    }


# 加载预训练模型和分词器
model_dir = os.path.join(BASE_DIR, 'Qwen2-0.5B')  # 模型目录路径
tokenizer = AutoTokenizer.from_pretrained(model_dir, use_fast=False, trust_remote_code=True)  # 加载分词器
model = AutoModelForCausalLM.from_pretrained(model_dir, device_map=device, torch_dtype=torch.bfloat16)  # 加载模型
model.enable_input_require_grads()  # 开启梯度检查点时,要执行该方法

# 加载、处理数据集和测试集
train_dataset_path = os.path.join(BASE_DIR, 'zh_cls_fudan-news', 'train.jsonl')  # 训练数据集路径
test_dataset_path = os.path.join(BASE_DIR, 'zh_cls_fudan-news', 'test.jsonl')  # 测试数据集路径

train_jsonl_new_path = os.path.join(BASE_DIR, 'train.jsonl')  # 处理后的训练数据集路径
test_jsonl_new_path = os.path.join(BASE_DIR, 'test.jsonl')  # 处理后的测试数据集路径

# 如果处理后的训练数据集文件不存在,则调用dataset_jsonl_transfer函数进行转换
if not os.path.exists(train_jsonl_new_path):
    dataset_jsonl_transfer(train_dataset_path, train_jsonl_new_path)
# 如果处理后的测试数据集文件不存在,则调用dataset_jsonl_transfer函数进行转换
if not os.path.exists(test_jsonl_new_path):
    dataset_jsonl_transfer(test_dataset_path, test_jsonl_new_path)

# 得到微调数据集
train_df = pd.read_json(train_jsonl_new_path, lines=True)  # 读取处理后的训练数据集
train_ds = Dataset.from_pandas(train_df)  # 将训练数据集转换为Dataset对象
train_dataset = train_ds.map(process_func, remove_columns=train_ds.column_names)  # 对训练数据集进行预处理

# 创建LoRA配置
config = LoraConfig(
    task_type=TaskType.CAUSAL_LM,  # 任务类型为因果语言模型
    target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],  # 需要应用LoRA的模块
    inference_mode=False,  # 训练模式
    r=8,  # Lora 秩
    lora_alpha=32,  # Lora alpha,具体作用参见 Lora 原理
    lora_dropout=0.1,  # Dropout 比例
)

# 将LoRA应用于模型
model = get_peft_model(model, config)

# 创建微调参数
args = TrainingArguments(
    output_dir=os.path.join(BASE_DIR, 'output', 'Qwen2-0.5B'),  # 输出目录
    per_device_train_batch_size=4,  # 每个设备的训练批次大小
    gradient_accumulation_steps=4,  # 梯度累积步数
    logging_steps=10,  # 日志记录步数
    num_train_epochs=2,  # 训练轮数
    save_steps=100,  # 模型保存步数
    learning_rate=1e-4,  # 学习率
    save_on_each_node=True,  # 在每个节点上保存模型
    gradient_checkpointing=True,  # 使用梯度检查点
    report_to="none",  # 不报告到任何平台
)

# SwanLab微调过程回调数据
swanlab_callback = SwanLabCallback(project="qwen", experiment_name="Qwen2-0.5B")

# 创建Trainer对象,用于管理模型的训练过程
trainer = Trainer(
    model=model,
    args=args,
    train_dataset=train_dataset,
    data_collator=DataCollatorForSeq2Seq(tokenizer=tokenizer, padding=True),
    callbacks=[swanlab_callback],
)

# 开始微调
trainer.train()

# 模型结果结果评估
def predict(messages, model, tokenizer):
    # 构造模型输入的文本
    text = tokenizer.apply_chat_template(
        messages,
        tokenize=False,
        add_generation_prompt=True
    )
    # 对输入文本进行分词并转换为模型输入
    model_inputs = tokenizer([text], return_tensors="pt").to(device)

    # 生成模型的输出
    generated_ids = model.generate(
        model_inputs.input_ids,
        max_new_tokens=512
    )
    # 提取生成的输出部分
    generated_ids = [
        output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)
    ]

    # 将生成的输出解码为文本
    return tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]

# 模型评估:获取测试集的前10条测试数据
test_df = pd.read_json(test_jsonl_new_path, lines=True)[:10]

test_text_list = []
for index, row in test_df.iterrows():
    # 构造系统指令
    instruction = '你是一个文本分类领域的专家,你会接收到一段文本和几个潜在的分类选项列表,请输出文本内容的正确分类'
    input_value = row['input']  # 获取输入文本

    # 构造输入消息
    messages = [
        {"role": "system", "content": f"{instruction}"},
        {"role": "user", "content": f"{input_value}"}
    ]

    # 调用predict函数进行预测
    response = predict(messages, model, tokenizer)
    messages.append({"role": "assistant", "content": f"{response}"})  # 添加模型的响应

    # 构造结果文本
    result_text = f"{messages[0]}\n\n{messages[1]}\n\n{messages[2]}"
    test_text_list.append(swanlab.Text(result_text, caption=response))  # 将结果添加到列表中

# 使用swanlab记录预测结果
swanlab.log({"Prediction": test_text_list})
swanlab.finish()  # 结束实验日志记录

结果评估与应用

模型预测函数

定义 predict 函数,用于模型预测。该函数接受输入消息 messages、模型和分词器作为参数,执行以下操作:

  • 使用分词器的 apply_chat_template 方法,将输入消息格式化为模型可接受的文本模板,并添加生成提示。

  • 对格式化后的文本进行分词,生成模型输入。

  • 将模型输入传输到设备上,并调用模型的 generate 方法生成文本。

  • 从生成的文本中提取新生成的部分,并将其解码为可读文本。

模型评估

从测试集中选取前 10 条数据进行模型评估。对于每条测试数据,构建包含系统提示和用户输入的对话消息,调用 predict 函数获取模型生成的分类结果。将输入消息、生成的分类结果以及预期的正确分类组合成完整的对话文本,并使用 SwanLab 的 Text 类进行可视化展示。最后,将所有评估结果记录到 SwanLab 平台上,并结束实验记录。

通过本次微调实战,我成功地将 Qwen2-0.5B 模型应用于文本分类任务,并在 SwanLab 平台上详细记录了训练和评估过程。这不仅提升了模型在特定任务上的性能,也为后续的模型优化和应用提供了宝贵的经验和数据支持。

# 模型结果评估
import pandas as pd  # 导入pandas库,用于数据处理和分析
import torch  # 导入PyTorch库,用于深度学习模型的加载和推理
from modelscope import AutoTokenizer  # 导入AutoTokenizer,用于自动加载预训练模型的分词器
from transformers import AutoModelForCausalLM  # 导入AutoModelForCausalLM,用于加载预训练的因果语言模型
from transformers import TrainingArguments, Trainer, DataCollatorForSeq2Seq  # 导入训练相关的类,虽然这里未直接使用
import os  # 导入os模块,用于路径操作
import swanlab  # 导入swanlab模块,用于实验日志记录和结果可视化

# 权重根目录,定义模型和数据存储的根目录
BASE_DIR = r'F:\Projects\Python\Model'

# 设备名称,根据是否有可用的CUDA设备选择使用GPU或CPU
device = 'cuda' if torch.cuda.is_available() else 'cpu'

# 创建一个SwanLab项目,用于记录实验日志和结果
swanlab.init(
    workspace="xxzz",  # 工作空间名称
    project="qwen",  # 项目名称
    experiment_name="Qwen2-0.5B-Predict"  # 实验名称
)

# 加载预训练模型和分词器
model_dir = os.path.join(BASE_DIR, 'merge', 'transformers')  # 模型目录路径
tokenizer = AutoTokenizer.from_pretrained(model_dir, use_fast=False, trust_remote_code=True)  # 加载分词器
model = AutoModelForCausalLM.from_pretrained(model_dir, device_map=device, torch_dtype=torch.bfloat16)  # 加载模型

# 明确设置 pad_token,确保模型在处理时有明确的填充标记
if tokenizer.eos_token is None:
    eos_token = tokenizer.convert_ids_to_tokens(tokenizer.eos_token_id)  # 获取EOS标记
    tokenizer.add_special_tokens({'eos_token': eos_token})  # 添加EOS标记
if tokenizer.pad_token is None:
    pad_token = tokenizer.convert_ids_to_tokens(tokenizer.eos_token_id + 1)  # 确保 pad_token 与 eos_token 不同
    tokenizer.add_special_tokens({'pad_token': pad_token})  # 添加PAD标记

test_jsonl_new_path = os.path.join(BASE_DIR, 'test.jsonl')  # 测试数据集路径

# 模型结果评估函数,用于生成模型的预测结果
def predict(messages, model, tokenizer):
    # 构造模型输入的文本
    text = tokenizer.apply_chat_template(
        messages,
        tokenize=False,
        add_generation_prompt=True
    )
    # 对输入文本进行分词并转换为模型输入
    model_inputs = tokenizer([text], return_tensors="pt", padding=True, truncation=True, max_length=512).to(device)

    # 确保 attention_mask 被设置
    if 'attention_mask' not in model_inputs:
        model_inputs['attention_mask'] = torch.ones_like(model_inputs['input_ids'])

    # 生成模型的输出
    generated_ids = model.generate(
        model_inputs.input_ids,
        attention_mask=model_inputs.attention_mask,
        max_new_tokens=512
    )
    # 提取生成的输出部分
    generated_ids = [
        output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)
    ]

    # 将生成的输出解码为文本
    return tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]

# 模型评估:获取测试集的第11到50条测试数据
test_df = pd.read_json(test_jsonl_new_path, lines=True)[10:50]

test_text_list = []  # 用于存储预测结果的列表
for index, row in test_df.iterrows():
    input_value = row['input']  # 获取input字段的值

    # 构造输入消息
    messages = [
        {"role": "system", "content": "你是一个文本分类领域的专家,你会接收到一段文本和几个潜在的分类选项列表,请输出文本内容的正确分类"},
        {"role": "user", "content": f"{input_value}"}
    ]

    # 调用predict函数进行预测
    response = predict(messages, model, tokenizer)
    messages.append({"role": "assistant", "content": f"{response}"})  # 添加模型的响应

    # 构造结果文本
    result_text = f"{messages[0]}\n\n{messages[1]}\n\n{messages[2]}"
    print("result_text", messages[2])  # 打印预测结果
    test_text_list.append(swanlab.Text(result_text, caption=response))  # 将结果添加到列表中

# 使用swanlab记录预测结果
swanlab.log({"Prediction": test_text_list})
swanlab.finish()  # 结束实验日志记录

本地大模型部署:使用 Ollama 服务

Ollama 是一款强大的工具,能够帮助你在本地部署和运行大语言模型。它支持多种模型,如 Llama 3.3、Phi 3、Mistral、Gemma 2 等,并允许你自定义和创建自己的模型。Ollama 提供了简洁易用的命令行界面,让你能够轻松管理和使用大模型。

下载与安装

  • 访问 Ollama 官网,下载适用于 macOS、Linux 或 Windows 的安装包。

  • 安装完成后,你就可以开始使用 Ollama 的各种功能了。

常用命令

  • 查看 Ollama 中的大模型:运行 ollama list 命令,即可查看 Ollama 中已安装和可用的大模型。

  • 运行大模型:使用 ollama run xxx 命令,其中 xxx 是你想要运行的模型名称。例如,ollama run qwen 将运行名为 "qwen" 的模型。

  • 启动 API 服务:执行 ollama serve 命令,Ollama 将启动 API 服务,默认监听在 http://127.0.0.1:11434/ 端口上。启动 API 服务后,你需要运行一个大模型,才能通过 API 进行调用。

    命令

    描述

    ollama serve

    启动 Ollama 服务。

    ollama create /path/to/Modelfile

    使用 Modelfile 创建一个新模型。

    ollama show model_name

    查看特定模型的详细信息。

    ollama listollama ls

    列出本地所有可用的模型。

    ollama run model_name

    运行一个已安装的模型。

    ollama ps

    显示当前正在运行的模型列表。

    ollama rm model_name

    删除一个已安装的模型。

    ollama -vollama --version

    显示当前 Ollama 工具的版本信息。

    ollama cp old_model new_model

    复制一个模型到另一个位置或给定名称的地方。

    ollama pull model_name

    从模型注册表中拉取一个模型。

    ollama push model_name

    将本地模型推送到模型注册表中。

    ollama help

    查看 Ollama 支持的所有命令及其用法。

    journalctl -u ollama

    查看作为启动服务运行的 Ollama 的日志。

    sudo systemctl stop ollama

    停止 Ollama 服务。

    sudo systemctl disable ollama

    禁用 Ollama 服务。

    sudo rm $(which ollama)

    删除 Ollama 二进制文件。

    sudo rm -r /usr/share/ollama

    删除已下载的模型以及 Ollama 服务用户和组。

    sudo userdel ollama

    删除 Ollama 服务用户。

    sudo groupdel ollama

    删除 Ollama 服务组。

导入 GGUF 模型

要导入 GGUF 模型,请创建一个包含以下内容的 Modelfile

FROM /path/to/file.gguf
// FROM F:\Projects\Python\Model\gguf\Qwen2-0.5B\Qwen2-0.5B.gguf

API 调用方式

  • 直接调用:通过 http://127.0.0.1:11434/api/generate 端点进行调用。例如,发送以下 JSON 数据:

    {
      "model": "qwen",
      "prompt": "使用中文介绍一下你自己",
      "stream": false
    }

    这将使用 "qwen" 模型生成文本,回答关于自己的问题,并以中文输出。

  • OpenAI 风格调用:如果你习惯使用 OpenAI 的 API 格式,可以通过 http://127.0.0.1:11434/v1 端点进行调用。这种方式让你能够以熟悉的方式与本地部署的大模型进行交互。

    <template>
      <div class="container">
        <h2>我是Qwen</h2>
        <div class="question-anwser" id="scroll-wrapper">
          <div class="content" v-for="(item, index) in msgList">
            <div class="question">
              <div>
                {{ item.content }}
              </div>
            </div>
            <div class="anwser" v-if="answerList[index]">
              <div>
                <VMdPreview :text="answerList[index]" />
              </div>
            </div>
            <div class="anwser" v-else-if="!isDone">
              <div>
                <VMdPreview :text="answer" />
              </div>
            </div>
          </div>
        </div>
        <div class="chat-box">
          <div class="chat-input">
            <input type="text" v-model="msg" @keyup.enter="send" />
            <button @click="send">发送</button>
          </div>
        </div>
      </div>
    </template>
    
    <script setup>
    import { ref, onMounted } from "vue";
    import OpenAI from "openai";
    import VMdPreview from "@kangc/v-md-editor/lib/preview";
    import "@kangc/v-md-editor/lib/style/preview.css";
    import githubTheme from "@kangc/v-md-editor/lib/theme/github.js";
    import "@kangc/v-md-editor/lib/theme/style/github.css";
    import hljs from "highlight.js";
    
    // ​ Server-Sent Events 服务器推送事件,简称 SSE,是一种服务端实时主动向浏览器推送消息的技术。
    
    VMdPreview.use(githubTheme, {
      Hljs: hljs,
    });
    
    const list = ref([{
      role: "system",
      content: "你好,我是基于Qwen开发的ZGer,有什么问题可以问我哦!",
    },{
      role: "system",
      content: "你的回答必须是中文。"
    }]); // 包含所有的问题和回答
    const msg = ref("");
    const msgList = ref([]);
    const answer = ref("");
    const answerList = ref([]);
    const isDone = ref(true);
    
    const send = () => {
      msgList.value.push({
        role: "user",
        content: msg.value,
      });
      list.value.push({
        role: "user",
        content: msg.value,
      });
      msg.value = "";
      getAnswer();
    };
    
    const openai = new OpenAI({
      baseURL: "http://localhost:11434/v1/",
      apiKey: "ollama",
      dangerouslyAllowBrowser: true,
    });
    
    async function getAnswer() {
      try {
        isDone.value = false;
        const response = await openai.chat.completions.create({
          messages: list.value,
          model: "qwen",
          // model: "llama3.2",
          stream: true,
        });
    
        for await (const chunk of response) {
          answer.value += chunk.choices[0].delta.content;
          // 判断是否结束
          if (chunk.choices[0].finish_reason == "stop") {
            answerList.value.push(answer.value);
            list.value.push({
              role: "system",
              content: answer.value,
            });
            answer.value = "";
            isDone.value = true;
          }
        }
      } catch (error) {
        console.error("Error in main:", error);
      }
    }
    
    // 实现浏览器滚动条自动滚动到底部
    onMounted(() => {
      const element = document.getElementById("scroll-wrapper");
    
      const observer = new MutationObserver((mutations) => {
        mutations.forEach((mutation) => {
          if (mutation.type === "childList") {
            element.scrollTop = element.scrollHeight;
          }
        });
      });
    
      observer.observe(element, {
        childList: true, // 监听子元素的添加和删除
        subtree: true, // 监听所有后代节点的变化
      });
    });
    </script>
    
    <style>
    html,
    body {
      margin: 0;
      padding: 0;
      overflow: hidden;
      background-color: rgb(243, 245, 250);
    }
    /* scroll样式 */
    ::-webkit-scrollbar {
      width: 8px;
      height: 8px;
    }
    ::-webkit-scrollbar-thumb {
      background-color: rgba(0, 0, 0, 0.2);
      border-radius: 10px;
    }
    ::-webkit-scrollbar-track {
      background-color: rgba(0, 0, 0, 0);
      border-radius: 10px;
    }
    
    .container {
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      height: 100vh;
      overflow: hidden;
      width: 80%;
      margin: 0 auto;
      /* text-align: center; */
      padding-top: 24px;
    
      .question-anwser {
        width: 80%;
        height: 80%;
        overflow-y: scroll;
        .content {
          /* display: flex; */
          flex-direction: column;
          justify-content: center;
          align-items: center;
          margin: 0 auto;
          .question {
            display: flex;
            justify-content: flex-end;
    
            > div {
              max-width: 40%;
              background-color: rgb(45, 101, 247);
              color: #ffffff;
              padding: 10px;
              border-radius: 10px;
              margin-bottom: 24px;
            }
          }
          .anwser {
            display: flex;
            justify-content: flex-start;
            > div {
              max-width: 80%;
              background-color: #ffffff;
              padding: 10px;
              border-radius: 10px;
              margin-bottom: 10px;
            }
          }
        }
      }
    
      .chat-box {
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        width: 80%;
        height: 20%;
        .chat-input {
          display: flex;
          justify-content: center;
          align-items: center;
          width: 100%;
          input {
            /* width: 80%; */
            flex: 1;
            height: 30px;
            border: 1px solid #f0f0f0;
            border-radius: 5px;
            padding: 5px;
            margin-right: 10px;
          }
          button {
            width: 100px;
            height: 40px;
            border: 1px solid #f0f0f0;
            border-radius: 10px;
            background-color: #8fc2e4;
            color: #fff;
          }
        }
      }
    }
    
    .github-markdown-body {
      padding: 16px;
    }
    </style>
    

将safetensors转换为gguf以部署到Ollama

要将 .safetensors 文件转换为 .gguf 文件,可以使用 llama.cpp 提供的工具和脚本。以下是详细的步骤:

步骤 1:克隆 llama.cpp 仓库

  • 使用 Git 克隆 llama.cpp 仓库:

    git clone https://github.com/ggerganov/llama.cpp.git
    cd llama.cpp

步骤 2:安装依赖

  • llama.cpp 目录下,安装所需的 Python 依赖:

    pip3 install -r requirements.txt

步骤 3:下载 Safetensors 模型文件

  • 确保你已经下载了 Safetensors 格式的模型文件,并将 model.safetensorsconfig.jsontokenizer.json 放置在一个文件夹中。

步骤 4:执行转换脚本

  • 使用 convert_hf_to_gguf.py 脚本将 Safetensors 模型转换为 GGUF 格式:

    python convert_hf_to_gguf.py /path/to/your/model/folder --outtype f16 --verbose --outfile /path/to/output/gguf/file.gguf
    • /path/to/your/model/folder 是包含 Safetensors 模型文件的目录。

    • --outtype f16 指定输出格式为 float16,你可以根据需要选择其他格式。

    • --verbose 用于输出详细信息。

    • --outfile 指定输出文件的路径。

步骤 5:验证转换结果

  • 转换完成后,检查输出目录中是否生成了 .gguf 文件。你可以使用 Ollama 或其他支持 GGUF 格式的工具来测试和使用该模型。

通过以上步骤,你可以将 Safetensors 格式的模型文件转换为 GGUF 格式,以便在 Ollama 或其他支持 GGUF 的平台上使用。


原模型和微调后的模型合并

from peft import PeftModel, PeftConfig
from transformers import AutoModelForCausalLM, AutoTokenizer
# 加载原始模型
model = AutoModelForCausalLM.from_pretrained(r'F:\Projects\Python\Model\Qwen2-0.5B')
tokenizer = AutoTokenizer.from_pretrained(r"F:\Projects\Python\Model\Qwen2-0.5B")

# 加载LoRA适配器
peft_config = PeftConfig.from_pretrained(r'F:\Projects\Python\Model\output\Qwen2-0.5B\checkpoint-500')
model = PeftModel.from_pretrained(model, r'F:\Projects\Python\Model\output\Qwen2-0.5B\checkpoint-500')

# 合并模型
model = model.merge_and_unload()

# 保存合并后的模型
model.save_pretrained(r"F:\Projects\Python\Model\merge\transformers", safe_serialization=True)
tokenizer.save_pretrained(r"F:\Projects\Python\Model\merge\transformers")

文章作者: xxzz
本文链接:
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 xxzz
NLP
喜欢就支持一下吧