因为原来那个微调环境,在我和'deekseep'的操作下被搞崩了,由于现在的条件限制,我们现在也用不了那个jupyter的可视化界面,那么接下来的微调操作就不需要这个东西了。下面我们来重新配置微调环境。后面我会把微调过程也写出来。

经过第一次的配置,发现微调环境有很多库,不同的模型适配不同的库,不同的库又要和其他的库进行配合,一旦其中一个出了冲突,就会有连锁反应,所以配置的时候一定要小心。最好有个强力的AI来指引。

关于目录结构

先把目录结构再次整理一下,目录结构太乱会影响心情。

logs:记录日志

merged:保存lora权重和原模型合并过的模型

ckpts:保存LoRa权重

code:保存运行代码

data:保存训练数据集

models:保存原始模型

tmp:一些临时文件保存

微调环境创建*

然后我准备创建两个环境。一个用来微调,一个用来调用和训练。分成两个防止出冲突。

#创建conda环境

conda create -n zy_unsloth python=3.10 -y
conda activate zy_unsloth

#升级基础工具

python -m pip install -U pip setuptools wheel

#这里升级了pip,setuptools,wheel

pip:用来安装包,旧版的pip不支持最新的.whl,解析复杂的依赖逻辑容易冲突

setuptools:python中负责调用编译器将其转化为可运行的状态。

wheel:成品仓库,.whl文件是编译好的成品,解压直接用,不需要在本地寻找C++编译器。

#升级PyTorch

它是微调的引擎,负责执行数学运算,管理显存等

原因:

1,硬件兼容显卡驱动CUDA,

2,新版可能引入新算法

3,性能优化

这里要从官网上面下载防止冲突。

pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121

#下载unsloth和训练依赖

pip install unsloth

我这里仅仅装了个unsloth,它就把其他的核心包给我装好了。以下代码完全没有执行的必要。

不要随意安装,如果一个包安装后出现爆红的现象,说明是环境出现冲突,检测微调环境是否可以继续使用,要马上找方法解决。解决不了就只能重装了。
pip install "transformers>=4.37.0" datasets accelerate peft trl sentencepiece
pip install bitsandbytes
pip install scikit-learn pandas tqdm

python - <<'PY'
import torch
import unsloth
import transformers
import bitsandbytes as bnb

print("torch:", torch.__version__)
print("cuda:", torch.cuda.is_available())
print("transformers:", transformers.__version__)
print("unsloth ok")
print("bitsandbytes ok")
PY

在终端执行,验证环境是否装好了,如果环境已经安装完成会有以下回显
 

如果有这些显示,说明必要的包已经装好了,并且导入成功。

冻结环境

有必要给环境冻结一下,给自己炼制一个悔蛊。

pip freeze > /workspace/weitiao/zy/logs/zy_unsloth_requirements.txt

以后想要这个环境直接创建一个环境然后执行:

pip install -r /workspace/weitiao/zy/logs/zy_unsloth_requirements.txt

可以得到一个一模一样的环境。

微调环境测试

然后我们来测试一下,是否可以跑通在这个微调环境下。

这里用个简单的数据集,测试一下

import os
from datasets import load_dataset
from unsloth import FastLanguageModel,is_bfloat16_supported
from transformers import TrainingArguments
from trl import SFTTrainer

BASE_MODEL = "/workspace/weitiao/zy/models/Qwen2.5-7B-Instruct"
TRAIN_FILE = "/workspace/weitiao/zy/data/train.jsonl"
VAL_FILE = "/workspace/weitiao/zy/data/val.jsonl"
OUTPUT_DIR = "/workspace/weitiao/zy/ckpts/qwen25_cyber_lora"

MAX_SEQ_LENGTH = 2048
LOAD_IN_4BIT = True

def format_example(example):
    instruction = example.get("instruction", "").strip()
    input_text = example.get("input", "").strip()
    output = example.get("output", "").strip()

    if input_text:
        text = (
            "<|im_start|>system\n"
            "你是一个专业的网络安全助手,回答应准确、清晰、审慎。\n"
            "<|im_end|>\n"
            "<|im_start|>user\n"
            f"{instruction}\n\n补充信息:{input_text}\n"
            "<|im_end|>\n"
            "<|im_start|>assistant\n"
            f"{output}"
            "<|im_end|>"
        )
    else:
        text = (
            "<|im_start|>system\n"
            "你是一个专业的网络安全助手,回答应准确、清晰、审慎。\n"
            "<|im_end|>\n"
            "<|im_start|>user\n"
            f"{instruction}\n"
            "<|im_end|>\n"
            "<|im_start|>assistant\n"
            f"{output}"
            "<|im_end|>"
        )
    return {"text": text}

def main():
    os.makedirs(OUTPUT_DIR, exist_ok=True)

    dataset = load_dataset(
        "json",
        data_files={"train": TRAIN_FILE, "validation": VAL_FILE}
    )
    dataset = dataset.map(format_example)

    model, tokenizer = FastLanguageModel.from_pretrained(
        model_name=BASE_MODEL,
        max_seq_length=MAX_SEQ_LENGTH,
        dtype=None,
        load_in_4bit=LOAD_IN_4BIT,
    )

    tokenizer.padding_side = "right"

    model = FastLanguageModel.get_peft_model(
        model,
        r=16,
        target_modules=[
            "q_proj", "k_proj", "v_proj", "o_proj",
            "gate_proj", "up_proj", "down_proj"
        ],
        lora_alpha=32,
        lora_dropout=0,
        bias="none",
        use_gradient_checkpointing="unsloth",
        random_state=3407,
        use_rslora=False,
        loftq_config=None,
    )

    training_args = TrainingArguments(
        output_dir=OUTPUT_DIR,
        per_device_train_batch_size=1,
        gradient_accumulation_steps=8,
        warmup_steps=10,
        num_train_epochs=3,
        learning_rate=2e-4,
        fp16 = not is_bfloat16_supported(),
        bf16 = is_bfloat16_supported(),
        logging_steps=5,
        optim="adamw_8bit",
        weight_decay=0.01,
        lr_scheduler_type="cosine",
        seed=3407,
        save_steps=50,
        eval_strategy="steps",
        eval_steps=50,
        save_total_limit=2,
        load_best_model_at_end=False,
        report_to="none",
    )

    trainer = SFTTrainer(
        model=model,
        tokenizer=tokenizer,
        train_dataset=dataset["train"],
        eval_dataset=dataset["validation"],
        dataset_text_field="text",
        max_seq_length=MAX_SEQ_LENGTH,
        packing=False,
        args=training_args,
    )

    trainer.train()
    model.save_pretrained(OUTPUT_DIR)
    tokenizer.save_pretrained(OUTPUT_DIR)
    print(f"LoRA adapter saved to: {OUTPUT_DIR}")

if __name__ == "__main__":
    main()

这个脚本是可以跑通的,说明环境没有问题。

脚本都干了什么

这个脚本是AI写的以至于我现在完全不知道它引入了什么东西,利用什么参数,虽然是个测试脚本,我们就以它为例来学习一下。

下面我要讲一下,我用这个脚本都做了什么。

BASE_MODEL = "/workspace/weitiao/zy/models/Qwen2.5-7B-Instruct" # 原始大脑 LOAD_IN_4BIT = True # 压缩模式

#由于原版模型需要15G的显存才能正常运行,现在给压缩到了5G左右的显存可以正常运行,然后有更多剩余显存才更有余力往里面塞东西。

#LoRA参数的引入。

model = FastLanguageModel.get_peft_model(

model, r = 16, # 参数 A:学习空间

lora_alpha = 32, # 参数 B:影响力

target_modules = ["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],# 参数 C:作用部位

lora_dropout = 0,

bias = "none", )

核心公式:

在大模型内部,每一个回答都是通过巨大的矩阵乘法计算出来的。

原始状态(被LoRA冻结): 模型原本的知识叫 W。当你问它问题时,它运行 Wx(x$ 是你的问题),得到一个普通回答。

LoRA 工作状态: 你引入了两个小矩阵 A 和 B。现在的计算变成了:

改变回答方向的原理:

r=16,就是定义了矩阵A和矩阵B的大小

A负责把问题x压缩到一个16维的小空间

B负责把这个16维的信息还原回模型的回答中

训练的过程就是不断替代A和B中的数值,使得BxA计算出来的结果,刚好抵消掉原模型的原始不必要回答,替换为你想要的安全专家回答

参数如何控制回答方向

a.修改r(Rank)--决定回答指向的细腻程度

如果r=4:这个小空间就太窄了,模型只能学会一些简单的关键词。回答问题也不会太标准。

如果r=16:空间足够大,模型不仅可以学会关键词,还可以学会网络安全的句式逻辑和语气。

如果r=1024:空间过大,模型会试图记住训练集中的每一句话,导致它失去了灵活变通能力

b.修改lora_alpha决定了回答指向的偏执程度

脚本中lora_alpha=32。它在数学上是一个系数(\frac{a}{r})

逻辑:如果\alpha\gamma的两倍,相当于给新知识加了两倍的扩音器

效果:即使原模型想说一句普通的话,由于LoRA这边的信号放大了两倍,最终回答会被强制扭转到数据集方向。

C. 修改target_models  --决定了指向的“覆盖范围”

脚本中写了:q_proj,v_proj,up_proj等等

q,k,v(注意力):修改这里,是让模型改变看问题的重点。比如看到"注入"这个词,普通模型关注的是"注进液体",微调后的模型会把注意力全部集中在"SQL注入"上。

up,down,gate(推理层/MLP):修改这里,让模型改变"思考的逻辑",它决定了模型如何把提取的重点组合成逻辑严密的回答。

是让大模型改变思考的逻辑,它决定了模型如何把提取到的重点组合成逻辑严密的回答。

为什么模型回答可以被指向其他地方

微调本质是在做“概率修正”,

(1)原模型:看到“如何看待注入”,它预测概率最高可能是:“医疗”“美容”方面

(2)微调成功过程中:当它预测到“医疗”时,脚本中的SFTTrainer发现与数据集中的"SQL注入"不符,就会产生一个误差信号。

(3)参数更新:这个误差信号传回给A和B矩阵

(4)最终指向:经过多次训练,A和B形成的\Delta W就像一个导航修正。现在模型看到“注入”词汇,Wx想往医疗方向跑,但是\left ( B\times A \right )x会产生强大拉力把它拉回到网络安全方面

 

教它说话(Prompt 模板)

text = (
    "<|im_start|>system\n你是一个专业的网络安全助手...<|im_end|>\n"
    "<|im_start|>user\n{instruction}\n<|im_end|>\n"
    "<|im_start|>assistant\n{output}<|im_end|>"
)

强制模型学会这种带有标签的回答格式。ChatML 格式

意义:通过不断看这种带有<|im_start|>的对话,模型学会了:看到“system”我就要进入专家角色,看到“user”我就要准备回答。这改变了模型的对话习惯。

训练参数控制

learning_rate = 2e-4,      # 学习率:讲课速度
num_train_epochs = 3,       # 课时:书看三遍
optim = "adamw_8bit",      # 优化器:学习方法

2e-4 (0.0002):这是一个稳健的学习速度,既能学会新知识,又不会让模型产生“幻觉”或者忘记原来的基本语言能力。

3Epochs :让模型把train.jsonl看三遍,形成记忆。

8-bit优化器:学习中减少思考成本。

微调环境就先到这。后面我会搞一个调用测试环境并且用大一点的数据集去微调,查看模型受到的影响。并进行压力测试。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐