目的: 在局域网中部署离线deepseek(Qwen模型通用,可直接替换代码中的模型路径),用自己专业领域的数据进行训练,形成某个行业的业务专家AI,可以供多人在局域网环境中在网页端进行交互对话。

硬件设施: 3070ti (8g显存),32G内存,win11系统,Anaconda虚拟环境,python编译

使用组件(全部开源免费):cuda、cudnn、nvidia显卡驱动、Anaconda虚拟环境、pycharm编译器、open-webui网页交互前端、Ollama并发交互管理、deepseek开源模型,以及其他所需的依赖库。
这些组件都有linux版本,windows上能部署,linux系统也能部署。

1.需要的环境包和模型

1.1 deepseek模型下载

本人硬件条件有限,选择的是DeepSeek-R1-7B-instruct原生模型,在加载时进行4-bit量化也能跑起来。下载模型的时候要注意是数学类模型还是通用类模型。
deepseek的官方Github地址:https://github.com/deepseek-ai
模型下载页面:https://huggingface.co/Qwen/Qwen2.5-7B-Instruct/tree/main
在这里插入图片描述
在这里插入图片描述

1.2 并发交互和web前端

(1)并发交互使用Ollama,下载地址:https://ollama.com/
双击运行下载的安装包,按照提示完成安装。安装完成后,Ollama 会自动在后台运行。你可以在系统托盘中看到一个羊驼图标。
一般会自动将ollama文件夹添加进系统变量,没有的话手动添加,还需要重开命令行窗口验证

ollama --version

Ollama 默认安装在你的用户目录下。打开资源管理器,在地址栏输入 %LOCALAPPDATA%\Programs\Ollama 并回车,里面应该有一个 ollama.exe 文件。 如果没找到,也可以在 C:\Program Files\Ollama 目录下找找看。

将路径添加到 PATH 环境变量:
①右键点击“此电脑”(或“我的电脑”),选择“属性”。
②点击“高级系统设置”。
③在弹出的窗口中,点击“环境变量”。
④ 在“系统变量”列表中,找到并选中 Path 变量,然后点击“编辑”。
⑤点击“新建”,然后粘贴你刚刚找到的 Ollama 文件夹路径(例如 C:\Users\你的用户名\AppData\Local\Programs\Ollama)。
⑥点击“确定”保存所有窗口。

(2)web前端使用open-webui
open-webui 仅支持python3.11及以上,否则安装会报错,因此conda创建虚拟环境时需要强制指定python版本,尤其版本号要加上小版本指定。

conda create -n open-webui-env python=3.11.9 

虚拟环境中安装open-webui

pip install open-webui -i https://pypi.tuna.tsinghua.edu.cn/simple

1.3 依赖环境

cuda、cudnn、pytorch、显卡驱动、pycharm、Anaconda的安装参考以下文章:

(1)总流程
https://blog.csdn.net/m0_55127902/article/details/135654771
https://blog.csdn.net/qq_48176053/article/details/147335005

(2)cuda
https://developer.nvidia.com/cuda-90-download-archive?target_os=Windows&target_arch=x86_64&target_version=10&target_type=exenetwork

(3)cudnn
https://developer.nvidia.com/rdp/cudnn-archive

(4)pytorch
https://pytorch.org/get-started/previous-versions/

(5)conda常用命令
https://blog.csdn.net/2303_78957394/article/details/146009614
https://comate.baidu.com/zh/page/lre5hkldtwh

推荐将完全配置好的conda虚拟环境完全打包,以后再部署直接解压然后激活就行。

2.deepseek模型的本地部署和运行

编译器里运行模型主要是为了测试模型文件是否完整,或者重新训练的模型文件能否正常运行。实际在局域网中交互时由Ollama自动加载指定的本地模型文件或文件夹。

2.1 运行前准备工作确认

(1)依赖库确认

pip install transformers accelerate bitsandbytes

(2)确保 model_path 指向正确的本地模型文件夹(里面应有 config.json、tokenizer.json 以及权重文件等)。

(3)如果你的显卡显存比较紧张(8GB 运行 7B 模型已经比较极限),可以适当降低 max_new_tokens 的值,比如改为 256,以节省显存。
对话历史管理:使用 messages 列表存储所有对话轮次,每个元素是一个字典,包含 role(user 或 assistant)和 content。这样模型每次都能看到完整的上下文。

2.2 运行代码简介

(1)自动构建 Prompt:apply_chat_template 是 Hugging Face 分词器提供的标准方法,它会根据模型要求的格式(比如 Qwen 的 ChatML 格式)自动拼接历史消息,并添加必要的控制标记(如 <|im_start|>、<|im_end|>)。强烈推荐使用这种方式,比自己手动拼接更可靠。

(2)生成参数调整:

max_new_tokens=512:控制每次回复的最大长度,可根据需要调整。

temperature=0.7、top_p=0.9:控制回答的随机性和多样性。

repetition_penalty=1.05:轻微惩罚重复内容,让回答更自然。

eos_token_id 和 pad_token_id:确保生成在正确的结束标记处停止。

(3)循环退出条件:输入 exit(不区分大小写)即可结束程序。

2.3连续对话完整代码(Transformers + 4-bit 量化)

from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
import torch

# 模型路径(替换为你本地的模型文件夹路径)
model_path = "./Qwen2.5-7B-Instruct"  # 例如下载到当前目录下的这个文件夹

# 1. 配置 4-bit 量化(如果你之前已量化保存,可以去掉这一部分,直接加载)
bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_use_double_quant=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=torch.float16
)

# 2. 加载分词器和模型
tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(
    model_path,
    quantization_config=bnb_config,
    device_map="auto",
    trust_remote_code=True
)

# 3. 初始化对话历史(使用 OpenAI 格式的 messages 列表)
messages = []

print("模型加载成功!开始连续对话(输入 'exit' 结束)\n")

# 4. 开始对话循环
while True:
    # 获取用户输入
    user_input = input("用户: ")
    if user_input.lower() == "exit":
        print("对话结束。")
        break

    # 将用户输入添加到历史消息
    messages.append({"role": "user", "content": user_input})

    # 使用分词器的 apply_chat_template 方法构建带历史的 prompt
    # 这个方法会自动处理角色标签和结束符,确保格式正确
    prompt = tokenizer.apply_chat_template(
        messages,
        tokenize=False,
        add_generation_prompt=True  # 添加助手的生成提示符
    )

    # 编码并移至 GPU
    inputs = tokenizer(prompt, return_tensors="pt").to("cuda")

    # 生成回复
    outputs = model.generate(
        **inputs,
        max_new_tokens=512,
        do_sample=True,
        temperature=0.7,
        top_p=0.9,
        repetition_penalty=1.05,
        eos_token_id=tokenizer.eos_token_id,
        pad_token_id=tokenizer.pad_token_id or tokenizer.eos_token_id
    )

    # 解码回复(去掉输入部分)
    response = tokenizer.decode(
        outputs[0][inputs.input_ids.shape[1]:],
        skip_special_tokens=True
    )

    # 将助手回复添加到历史消息
    messages.append({"role": "assistant", "content": response})

    # 打印助手回复
    print(f"助手: {response}\n")

3.deepseek模型的训练

3.1 训练数据结构示例

由于需要多轮对话,所以采用ShareGPT格式,它是一个JSON文件,列表中的每个元素代表一段完整的对话历史。
核心字段:conversations 是一个列表,包含多轮对话。
角色标识:“from”: “human” 表示用户,“from”: “gpt” 表示模型。
系统提示:“system” 字段是可选的,用于设定本次对话的系统角色

在 ShareGPT 格式中,from 字段的值并没有硬性规定必须为 “human” 或 “gpt”。它的作用只是标识说话的角色,方便你在预处理时将其映射为模型所识别的角色名称。只要在训练代码的数据预处理函数中,将这些自定义的角色名称统一转换为模型期望的 role 字段,即 “user” 或 “assistant”:

if turn['from'] == '顾客':
    role = "user"
else:
    role = "assistant"
[
    {
        "instruction": "你是谁?",
        "input": "",
        "output": "我是一个由CSDN迪菲赫尔曼开发和维护的大语言模型。"
    },
    {
        "instruction": "请解释一下什么是机器学习",
        "input": "",
        "output": "机器学习是一门人工智能科学,它让计算机能够通过数据学习,而不是被明确编程..."[
    {
        "conversations": [
            {
                "from": "human",
                "value": "你好,请介绍一下你自己。"
            },
            {
                "from": "gpt",
                "value": "你好!我是一个由CSDN迪菲赫尔曼开发和维护的AI助手,很高兴为你服务!"
            },
            {
                "from": "human",
                "value": "你能做什么?"
            },
            {
                "from": "gpt",
                "value": "我可以帮你解答问题、提供建议、辅助学习等。"
            }
        ],
        "system": "你是一个乐于助人的助手。"  
    },
    {
        "conversations": [
            {
                "from": "human",
                "value": "如何学习Python?"
            },
            {
                "from": "gpt",
                "value": "学习Python可以从基础语法开始..."
            }
        ]
    }
]
    },
    {
        "instruction": "翻译下面的话成英文",
        "input": "今天天气真好。",
        "output": "The weather is nice today."
    }
]

3.2 训练代码

(1)运行前确认依赖库已安装

pip install torch transformers accelerate peft bitsandbytes datasets trl scipy

transformers若报错建议检查版本,在 transformers 4.x 到 5.x 的大版本升级中,API 发生了重大变化,可降级至4.46.3

pip uninstall transformers -y
pip install transformers==4.46.3

(2)训练注意事项

(3)完整的微调训练代码如下
①针对8GB显存的优化:代码中已经设置了 per_device_train_batch_size=1、gradient_accumulation_steps=8 和 max_length=1024。如果你的显存还是不够,可以进一步降低 max_length 或 gradient_accumulation_steps。

②数据量:不要指望用十几条数据就让模型产生质变。针对特定任务,准备几百到上千条高质量样本是基础。如果是改变模型的“自我认知”,几十条精心设计的样本也能生效。

③过拟合:如果你的数据集很小(如几百条),可以适当降低学习率(如 1e-4)和训练轮次(如 epochs=3),并观察loss值。

④保存的产物:训练完成后,output_dir 里保存的是 LoRA适配器权重,它非常小(几十MB)。要使用微调后的模型,你需要:
*推理时加载:使用 PeftModel.from_pretrained(base_model, adapter_path) 来合并并推理。
*永久合并:使用脚本将LoRA权重合并回原模型,生成一个新的完整模型。

⑤训练参数
num_train_epochs,训练轮数。如果数据集有 1000 条样本,per_device_train_batch_size=1,gradient_accumulation_steps=8,那么每个 epoch 的实际更新步数为:

步数 = ceil(样本数 / (per_device_train_batch_size * gradient_accumulation_steps))

即 1000 / (1×8) = 125 步。训练3个 epoch 总共 125*3 = 375 步

小数据集(几百条):通常 3-5 个 epoch 足够
中等数据集(几千条):1-3 个 epoch 常见
大数据集(几万条以上):可能只需 1 个 epoch

max_steps,最大更新步数(优先级更高)。如果同时设置了 max_steps,它会覆盖 num_train_epochs,训练将在达到指定步数后停止,无论是否完成所有 epoch。常用于调试、控制总训练时间

training_args = TrainingArguments(
    num_train_epochs=3,
    max_steps=500,   # 最多训练500步
    ...
)

此时,即使 3 个 epoch 需要 375 步,但 max_steps=500 更大,所以实际会跑完 500 步(可能超过 3 个 epoch);反之,如果 max_steps=200,则训练 200 步后停止,不足 3 个 epoch。

per_device_train_batch_size:单卡批大小
gradient_accumulation_steps:梯度累积步数
数据集大小:样本总数越多,每个 epoch 的步数越多
在这里插入图片描述

完整的训练代码如下:

import torch
from transformers import (
    AutoTokenizer,
    AutoModelForCausalLM,
    BitsAndBytesConfig,
    TrainingArguments,
    Trainer,
    DataCollatorForLanguageModeling
)
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
from datasets import load_dataset
import os

# ==================== 配置区域 ====================
model_path = "./Qwen2.5-7B-Instruct"  # 你的模型下载路径
data_file = "./my_chat_data.json"      # 你的训练数据文件路径
output_dir = "./qwen_lora_output"      # 训练输出目录

# 量化配置 (4-bit QLoRA - 显存优化核心)
bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_use_double_quant=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=torch.bfloat16
)

# LoRA配置
lora_config = LoraConfig(
    r=8,                    # LoRA秩,8-16之间,8更省显存
    lora_alpha=16,          # 缩放参数,通常是r的两倍
    target_modules=["q_proj", "k_proj", "v_proj", "o_proj"], # 常见模块
    lora_dropout=0.05,
    bias="none",
    task_type="CAUSAL_LM"
)

# 训练参数配置
training_args = TrainingArguments(
    output_dir=output_dir,
    per_device_train_batch_size=1,          # 8GB显存,batch_size必须为1
    gradient_accumulation_steps=8,           # 梯度累积,模拟batch_size=8
    num_train_epochs=3,                      # 训练轮次,数据量少可以增加
    learning_rate=2e-4,                      # LoRA通常用稍高学习率
    fp16=True,                                # 使用混合精度
    logging_steps=10,
    save_strategy="epoch",
    save_total_limit=2,
    remove_unused_columns=False,
    report_to="none"
)

# ==================== 加载模型和分词器 ====================
print("正在加载分词器...")
tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)
# 设置padding token,因为Qwen的tokenizer默认没有
if tokenizer.pad_token is None:
    tokenizer.pad_token = tokenizer.eos_token

print("正在加载模型(4-bit量化)...")
model = AutoModelForCausalLM.from_pretrained(
    model_path,
    quantization_config=bnb_config,
    device_map="auto",
    trust_remote_code=True
)

# 为k-bit训练准备模型
model = prepare_model_for_kbit_training(model)
# 应用LoRA
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()  # 打印可训练参数量

# ==================== 加载和预处理数据 ====================
print("正在加载数据集...")
# 以JSON格式加载数据,这里假设使用我们上面提到的ShareGPT格式
dataset = load_dataset('json', data_files=data_file, split='train')

def format_sharegp_toconversations(example):
    """将ShareGPT格式的数据处理成模型能理解的文本格式"""
    # 使用apply_chat_template是标准做法
    messages = example['conversations']
    # 将列表中的每条消息转换为 {"role": "user"/"assistant", "content": "xxx"} 格式
    formatted_messages = []
    for turn in messages:
        role = "user" if turn['from'] == 'human' else "assistant"
        formatted_messages.append({"role": role, "content": turn['value']})
    # 使用tokenizer的聊天模板将消息列表转换成模型输入的文本
    text = tokenizer.apply_chat_template(
        formatted_messages,
        tokenize=False,
        add_generation_prompt=False  # 训练时不加生成提示
    )
    return {"text": text}

# 如果你的数据是Alpaca格式,可以用这个函数
def format_alpaca_to_text(example):
    if example.get("input", ""):
        text = f"Instruction: {example['instruction']}\nInput: {example['input']}\nResponse: {example['output']}"
    else:
        text = f"Instruction: {example['instruction']}\nResponse: {example['output']}"
    return {"text": text}

# 根据你的数据格式,取消注释下面一行
dataset = dataset.map(format_sharegp_toconversations)
# dataset = dataset.map(format_alpaca_to_text)

def tokenize_function(examples):
    return tokenizer(
        examples["text"],
        truncation=True,
        max_length=1024,  # 根据显存调整,显存小就调低一点
        padding=False,
        return_tensors=None
    )

print("正在Tokenize数据集...")
tokenized_dataset = dataset.map(tokenize_function, batched=True, remove_columns=dataset.column_names)

# 数据整理器,用于动态padding
data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm=False)

# ==================== 开始训练 ====================
print("开始训练...")
trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=tokenized_dataset,
    data_collator=data_collator,
    tokenizer=tokenizer,
)

trainer.train()

# ==================== 保存模型 ====================
print("训练完成,保存模型...")
# 保存LoRA权重(只有几十MB)
model.save_pretrained(output_dir)
tokenizer.save_pretrained(output_dir)
print(f"模型已保存至 {output_dir}")

3.3 微调训练后模型的使用

(1)训练输出并不是模型
输出目录(例如 ./qwen_lora_output)中,应该有以下文件:
①adapter_config.json:LoRA 的配置文件。
②adapter_model.bin(或 .safetensors):训练好的 LoRA 权重。
③(可能还有 tokenizer 相关文件,如果保存了的话)
需要将 LoRA 适配器加载到原始的基础模型上才能使用。

(2)方法一:直接使用训练后的模型
这是最灵活的方式,推理时动态合并 LoRA 权重,不改变原始模型文件。

from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
from peft import PeftModel
import torch

# -------------------- 配置路径 --------------------
base_model_path = "./Qwen2.5-7B-Instruct"   # 基础模型路径
adapter_path = "./qwen_lora_output"         # 训练输出的 LoRA 适配器路径

# -------------------- 加载基础模型(4-bit量化,适配8GB显存) --------------------
bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_use_double_quant=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=torch.float16
)

print("正在加载基础模型...")
base_model = AutoModelForCausalLM.from_pretrained(
    base_model_path,
    quantization_config=bnb_config,          # 如果训练时未用量化,可以去掉此行
    device_map="auto",
    trust_remote_code=True
)

# -------------------- 加载分词器 --------------------
tokenizer = AutoTokenizer.from_pretrained(base_model_path, trust_remote_code=True)
if tokenizer.pad_token is None:
    tokenizer.pad_token = tokenizer.eos_token

# -------------------- 加载 LoRA 适配器 --------------------
print("正在加载 LoRA 适配器...")
model = PeftModel.from_pretrained(base_model, adapter_path)

# -------------------- 多轮对话 --------------------
messages = []   # 存储完整对话历史
print("\n模型加载成功!开始连续对话(输入 'exit' 结束)\n")

while True:
    # 获取用户输入
    user_input = input("用户: ")
    if user_input.lower() == "exit":
        print("对话结束。")
        break

    # 将用户输入加入历史
    messages.append({"role": "user", "content": user_input})

    # 使用聊天模板构建 prompt
    prompt = tokenizer.apply_chat_template(
        messages,
        tokenize=False,
        add_generation_prompt=True
    )

    # 编码并移至 GPU
    inputs = tokenizer(prompt, return_tensors="pt").to("cuda")

    # 生成回复
    outputs = model.generate(
        **inputs,
        max_new_tokens=512,
        do_sample=True,
        temperature=0.7,
        top_p=0.9,
        repetition_penalty=1.05,
        eos_token_id=tokenizer.eos_token_id,
        pad_token_id=tokenizer.pad_token_id
    )

    # 解码(去掉输入部分)
    response = tokenizer.decode(
        outputs[0][inputs.input_ids.shape[1]:],
        skip_special_tokens=True
    )

    # 将助手回复加入历史
    messages.append({"role": "assistant", "content": response})

    # 打印回复
    print(f"助手: {response}\n")

(3)方法二:合并原始模型和训练后的模型
合并模型会出现显存不足的错误,因此在加载基础模型时指定 device_map={“”: “cpu”},确保所有参数都在 CPU 上,然后执行合并。CPU 内存通常足够容纳 15GB 的模型(如果内存不足,可以尝试加载为 torch_dtype=torch.float16 减少一半内存占用)。

from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import PeftModel
import torch

base_model_path = "./Qwen2.5-7B-Instruct"
adapter_path = "./qwen_lora_output"
merged_save_path = "./qwen_lora_merged"

# 将基础模型加载到 CPU(避免 GPU 显存不足导致分散)
print("正在将基础模型加载到 CPU...")
base_model = AutoModelForCausalLM.from_pretrained(
    base_model_path,
    torch_dtype=torch.float16,          # 可选:半精度减少内存占用
    device_map={"": "cpu"},              # 关键:强制所有参数在 CPU
    trust_remote_code=True
)
tokenizer = AutoTokenizer.from_pretrained(base_model_path, trust_remote_code=True)

# 加载 LoRA 适配器(适配器会自动加载到与基础模型相同的设备,即 CPU)
print("正在加载 LoRA 适配器...")
model = PeftModel.from_pretrained(base_model, adapter_path)

# 合并权重并卸载 LoRA 结构(在 CPU 上执行)
print("正在合并 LoRA 权重...")
merged_model = model.merge_and_unload()

# 保存合并后的完整模型
print(f"保存合并后的模型至 {merged_save_path}")
merged_model.save_pretrained(merged_save_path)
tokenizer.save_pretrained(merged_save_path)
print("完成!")

3.4 自动将连续文段转换成训练数据

import json

def segments_to_sharegpt(segments, start_with="human", max_turns=None):
    """
    将文本段列表转换为 ShareGPT 格式的对话列表。

    Args:
        segments (list of str): 文本段列表。
        start_with (str): 第一个消息的角色,"human" 或 "gpt"。
        max_turns (int, optional): 每个对话的最大轮次(消息条数)。超过时切分为新对话。

    Returns:
        list: ShareGPT 格式的对话列表。
    """
    conversations = []
    current_conv = []
    roles = ["human", "gpt"]
    # 确定角色循环顺序
    if start_with == "gpt":
        roles = ["gpt", "human"]

    for i, text in enumerate(segments):
        role = roles[i % 2]  # 交替分配角色
        current_conv.append({"from": role, "value": text.strip()})

        # 如果指定了 max_turns 且当前对话已达到最大轮次,或者这是最后一段,则保存当前对话
        if (max_turns and len(current_conv) >= max_turns) or i == len(segments) - 1:
            # 只有至少包含一轮完整对话(human+gpt)才保存,但也可以根据需要调整
            if len(current_conv) >= 1:
                conversations.append({"conversations": current_conv})
            current_conv = []
            # 重置角色循环(让新对话从头开始)
            # 如果不重置,下一段的角色会接着上一段的最后一段,但通常新对话应该重新开始交替
            # 所以这里重置角色索引
            i_offset = i + 1
            # 但为了简单,我们让每个新对话都从 start_with 开始,重新计算 i % 2 会依赖全局索引,不好
            # 所以更好的做法是每次新对话重置角色计数器,但为了简单,这里重新赋值角色列表
            # 由于我们不知道下一段是新对话的开始,所以这里直接重置计数器,新对话从 start_with 开始
            # 但为了代码简洁,我们可以在外层循环中用局部变量控制
    return conversations

# 读取文段(这里假设文件每行一个段,你可以根据实际格式调整)
def read_segments(file_path, delimiter=None):
    with open(file_path, 'r', encoding='utf-8') as f:
        if delimiter:
            content = f.read()
            segments = content.split(delimiter)
        else:
            segments = [line.strip() for line in f if line.strip()]
    return segments

# 示例用法
if __name__ == "__main__":
    # 1. 从文件读取文段
    segments = read_segments("segments.txt")  # 每行一个段

    # 或者手动定义文段列表(用于演示)
    # segments = [
    #     "你好,请问今天天气怎么样?",
    #     "今天天气晴朗,气温15-25℃。",
    #     "明天呢?",
    #     "明天多云,可能有小雨。",
    #     "谢谢!",
    #     "不客气,祝您愉快!"
    # ]

    # 2. 转换为 ShareGPT 格式(每个对话最多包含4轮消息,即2个回合)
    conversations = segments_to_sharegpt(segments, start_with="human", max_turns=4)

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

    print(f"成功生成 {len(conversations)} 个对话,已保存到 sharegpt_data.json")

上述代码输出结果示例:

[
  {
    "conversations": [
      {"from": "human", "value": "你好,请问今天天气怎么样?"},
      {"from": "gpt", "value": "今天天气晴朗,气温15-25℃。"},
      {"from": "human", "value": "明天呢?"},
      {"from": "gpt", "value": "明天多云,可能有小雨。"}
    ]
  },
  {
    "conversations": [
      {"from": "human", "value": "谢谢!"},
      {"from": "gpt", "value": "不客气,祝您愉快!"}
    ]
  }
]

(1)角色必须正确交替:大多数模型期望对话以 “human” 开头,然后是 “gpt”,再 “human”… 不要连续出现两个相同角色。

(2)每条消息内容建议去除多余空白,保留原始文本即可。

(3)JSON 文件格式:确保最终文件是合法的 JSON(用 json.dump 会自动处理)。

(4)数据量少时:即使只有十几段,生成几个对话也是可以的,微调时依然有效。

4.Ollama 并发交互工具部署

可以直接在命令行输入例如“ollama run qwen2.5”,第一次运行时会自动下载对应的模型。关键是要让Ollama运行本地已经存在的模型文件。

4.1 链接模型

不管是原生模型(文件夹形式)还是gguf模型,ollama都可以提取。需要在模型或模型文件夹同级目录下创建没有后缀名的Modelfile文件,用文本编辑:

FROM 你的模型绝对路径/Qwen2.5-Math-7B-Instruct-Q6_K.gguf
或者
FROM 你的模型绝对路径/qwen2.5-7b-instruct.gguf

4.2 提取并转换模型

(2)打开终端 (CMD 或 PowerShell),输入以下命令

ollama create XXX-model -f ./Modelfile

需要等待很久
在这里插入图片描述
用ollama list命令确认导入成功:
在这里插入图片描述

4.3 运行模型测试(可选)

其实不需要在命令行里运行模型,对话时自动开启模型占用显存。
命令行输入:

ollama run XXX-model

4.4 配置ollama环境变量(重要)

配置并发量和上下文长度

命令行输入:

set OLLAMA_NUM_PARALLEL=3
set OLLAMA_CONTEXT_LENGTH=8192

需要重启 Ollama 服务才能生效,这一点很重要,否则对话时会报错。
重启要先退出ollama程序,然后在在命令行输入:

ollama serve

如果报错,进程被占用,用命令关闭

netstat -ano | findstr :11434

在这里插入图片描述
继续输入命令终止查询到的占用线程

taskkill /PID 20152 /F

5.open-webui 网页交互前端部署

5.1 启动服务

安装完成后在conda虚拟环境中启动open-webui服务,启动服务前需要将电脑改成固定IP,否则会报错。

open-webui serve

出现下图结果说明启动成功
在这里插入图片描述

5.2 注册管理员账号

(1)在浏览器中打开 http://localhost:8080,你会看到一个注册页面。在这里创建的第一个账户将自动成为管理员,拥有管理用户、连接模型等所有权限。
(2)填写用户名、邮箱和密码(邮箱可以随便填,只要格式正确即可,比如 admin@local.com)。
(3)点击注册,登录后就会进入主界面。
在这里插入图片描述

5.3 连接 Ollama 模型

(1)登录后,点击左下角的用户头像,选择 “管理员面板” (Admin Panel)。
在这里插入图片描述

(2)点击最上面的“设置”,跳出来的窗口中找到 “外部连接” (Connections) -> “Ollama”。一般会自动配置好。

在这里插入图片描述

(3)若没有配置好,则在 “Ollama 基础 URL” 一栏中,填入你的 Ollama 服务地址。由于 Open WebUI 和 Ollama 在同一台电脑上,填入 http://localhost:11434 即可。
(4)点击 “保存” (Save)。如果连接成功,点击窗口的“模型”会出现你自己的模型。
在这里插入图片描述

6 开始使用

6.1 允许普通用户创建账号

在管理员面板中开启:允许新用户注册,以及开启默认新用户为“用户”。
在这里插入图片描述

6.2 管理员将某个模型公开或对某个用户组公开

在管理员面板中选择“设置”→“模型”→某个具体模型后面“…”→选择“Make Public”。
在这里插入图片描述

6.3 局域网中普通用户浏览器中输入主机地址

(1)我的主机地址是:http://192.168.1.3:8080,普通用户进入注册页面,邮箱随便输,等待管理员激活账号。
(2)在管理员面板中激活普通用户的账号
在这里插入图片描述

(3)用户在对话框中选择模型进行对话
在对话框右下角的模型选择器中,选择你想要对话的模型,开始对话。
(管理员需要点击左上角“Open WebUI”图标回到主界面)

在这里插入图片描述
若报错如下,记得回去设置ollama的长下文长度环境变量。
在这里插入图片描述

成功对话如下图所示:
在这里插入图片描述

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐