DeepSeek-7B-chat 模型的 QLoRA 微调

在这篇博客中,我将介绍如何使用 QLoRA 技术对 DeepSeek-7B-chat 模型进行微调。QLoRA 是一种高效的微调方法,能够在有限的计算资源下对大型语言模型进行定制化训练。

1. 环境准备

首先,我们需要安装必要的 Python 包:

pip install transformers datasets pandas peft bitsandbytes accelerate

具体版本为:
transformers 4.38.0
peft 0.10.0
accelerate 0.26.0
dataset 2.14.6

2. 数据准备

我们使用 HarmonyOS 的训练数据进行微调。数据需要转换为标准的指令-输入-输出格式:

import pandas as pd
from datasets import Dataset

# 读取训练数据
df = pd.read_json('train.json')
ds = Dataset.from_pandas(df)

3. 模型加载与量化配置

我们使用 Hugging Face 的 transformers 库加载 DeepSeek-7B-chat 模型,并应用量化配置以减少内存使用:

安装huggingface_hub:

pip install -U huggingface_hub

windows:

$env:HF_ENDPOINT = "https://hf-mirror.com"

linux:

export HF_ENDPOINT=https://hf-mirror.com 

下载:

huggingface-cli download deepseek-ai/deepseek-llm-7b-chat --local-dir ./model_temp/deepseek-llm-7b-chat --local-dir-use-symlinks False
from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig

# 加载分词器
tokenizer = AutoTokenizer.from_pretrained('model_tmp/deepseek-llm-7b-chat/', use_fast=False, trust_remote_code=True)
tokenizer.padding_side = 'right'

# 创建量化配置
quantization_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_compute_dtype=torch.half,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_use_double_quant=True
)

# 加载模型
model = AutoModelForCausalLM.from_pretrained(
    'model_tmp/deepseek-llm-7b-chat/', 
    trust_remote_code=True, 
    torch_dtype=torch.half, 
    low_cpu_mem_usage=True,
    quantization_config=quantization_config
)

4. 数据处理与格式化

我们定义一个数据处理函数,将训练数据格式化为模型可以理解的输入:

def process_func(example):
    MAX_LENGTH = 384
    instruction = tokenizer(f"User: {example['instruction']+example['input']}\\n\\n", add_special_tokens=False)
    response = tokenizer(f"Assistant: {example['output']}<|end▁of▁sentence|>", add_special_tokens=False)
    input_ids = instruction["input_ids"] + response["input_ids"] + [tokenizer.pad_token_id]
    attention_mask = instruction["attention_mask"] + response["attention_mask"] + [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
    }

tokenized_id = ds.map(process_func, remove_columns=ds.column_names)

5. LoRA 配置与应用

我们使用 PEFT 库配置和应用 LoRA,以减少训练参数数量:

from peft import LoraConfig, TaskType, get_peft_model

# 配置LoRA
config = LoraConfig(
    task_type=TaskType.CAUSAL_LM, 
    target_modules=["q_proj", "k_proj", "v_proj", "o_proj"],
    inference_mode=False,
    r=4,
    lora_alpha=32,
    lora_dropout=0.1
)

# 应用LoRA配置到模型
model = get_peft_model(model, config)

6. 训练配置与执行

我们使用 transformers 库的 Trainer 类进行训练:

from transformers import TrainingArguments, Trainer

# 配置训练参数
args = TrainingArguments(
    output_dir="./output/DeepSeek",
    per_device_train_batch_size=16,
    gradient_accumulation_steps=1,
    logging_steps=10,
    num_train_epochs=30,
    save_steps=100,
    learning_rate=1e-4,
    save_on_each_node=True,
    gradient_checkpointing=True,
    optim="paged_adamw_32bit"
)

# 创建训练器
trainer = Trainer(
    model=model,
    args=args,
    train_dataset=tokenized_id,
    data_collator=DataCollatorForSeq2Seq(tokenizer=tokenizer, padding=True),
)

# 开始训练
trainer.train()

7. 模型测试与保存

训练完成后,我们可以测试模型并保存结果:

def test_model(text):
    inputs = tokenizer(f"User: {text}\\n\\n", return_tensors="pt")
    outputs = model.generate(**inputs.to(model.device), max_new_tokens=100)
    result = tokenizer.decode(outputs[0], skip_special_tokens=True)
    print(f"输入: {text}")
    print(f"输出: {result}")
    return result

# 测试示例
test_model("在ArkTS中,如何为一个包含imageUrl(字符串类型)和isAdd(布尔类型)属性的类创建构造函数?")

# 保存模型
model.save_pretrained("./output/DeepSeek/final_model")

8. 等待运行

在这里插入图片描述

<think> 首先,用户的问题是关于在微调DeepSeek-7B模型时如何处理输入数据。具体来说,用户问:是在输入input后得到output计算损失,还是将input和output一起输入,让模型从input续写到output。 参考用户提供的引用,我需要整合信息: - 引用[1]:介绍了QLoRA技术微调DeepSeek-7B-chat模型,涉及环境准备。 - 引用[2]:描述了DeepSeek-R1-Distill-Qwen-7B模型,基于Qwen架构,适用于NLP任务。 - 引用[3]:用户将文本喂给deepseek,数据集格式是{ "instruction": "...", "input": "", "output": "..." }。 - 引用[4]:关于LLaMA-Factory微调DeepSeek-R1-Distill-Qwen-7B,使用医疗诊断数据,转化为Alpaca格式{ "instruction": "...", "input": "...", "output": "..." }。 用户的核心问题是:在微调时,是只输入input让模型输出output,然后计算损失;还是将input和output一起输入,让模型从input续写到output。 在监督式微调(SFT)中,通常的模式是:给定一个上下文(包括instruction和input),让模型生成output。损失计算是基于模型的输出与预期output的差异。 回顾引用[4],它说LLaMA-Factory支持Alpaca数据格式,格式类似于{ "instruction": "...", "input": "...", "output": "..." }。在微调时,模型会将instruction和input拼接起来作为输入,然后让模型预测output。损失计算是模型生成的output与真实output的交叉熵损失。 在标准transformer微调中,输入序列是上下文(如instruction + input),目标序列是output。模型训练时,输入整个序列(包括上下文和目标),但训练时通过掩码确保模型只能基于上下文预测目标。具体来说: - 输入序列:通常是将instruction、input和output拼接在一起,但在训练时,损失只在output部分计算。 - 例如,输入:"[Instruction] ... [Input] ... [Output] ..." - 在自回归语言模型中,损失函数计算时,忽略input部分的位置,只针对output部分的token计算损失。 用户描述的两种方式: 1. 输入input,得到output计算损失:这可能意味着只输入input部分,让模型输出output,然后比较输出和真实output。 2. 输入input和output一起,让模型从input续写:这更接近标准做法,因为模型需要基于输入生成输出序列。 在基于因果语言模型(如GPT风格的模型)的微调中,标准方法是: - 将整个输入序列(instruction + input)和目标output拼接成一个序列。 - 模型在训练时,对于每个位置,预测下一个token,但损失只在output部分计算。 - 也就是说,在计算损失时,input部分的token被忽略;损失基于output部分的预测。 所以,在DeepSeek-7B的微调中,likely是将input和output一起输入,但模型被训练来从input生成output。 从引用[4]:使用LLaMA-Factory微调,支持Alpaca格式。Alpaca格式是用于指令微调的,其中输入是instruction和input的拼接,输出是output。 在实现中,输入字符串是:instruction + input,然后目标输出是output。训练时,损失基于模型对output的预测。 因此,对于DeepSeek-7B,应该是将input(可能包括instruction)输入,让模型生成output,然后计算损失。technically,输入序列包括上下文和目标的前缀,但损失只针对目标部分。 具体步骤: - 数据格式:每个样本有instruction, input, output。 - 输入序列:格式化后,如"Below is an instruction...\n### Instruction:\n{instruction}\n### Input:\n{input}\n### Response:\n" - 然后模型应该生成response(即output)。 - 损失计算:比较模型生成的token序列与真实output的序列,使用交叉熵损失。 在用户提供的引用[3]中,数据集格式是{ "instruction": "...", "input": "", "output": "..." },其中input有时为空,所以可能只输入instruction。 在[4]中,类似。 所以,回答用户:在微调时,通常是将input(包括instruction)输入,让模型预测output,然后计算损失。而不是让模型做扩写从头开始。 标准做法是:输入上下文(instruction + input),然后训练模型生成output。损失只在output部分计算。 例如,在Hugging Face的transformers库中,使用`text-generation`任务时,输入是前缀,目标是后缀。 现在,系统指令要求: - 所有行内数学表达式用$...$格式。 - 独立公式用$$...$$。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾添加引用标识。 用户的问题是中文的,所以我用中文回答。 回答结构:清晰、逐步解决问题。 最后生成相关问题。 回答内容: 1. 确认微调方法:在监督式微调DeepSeek-7B时,通常是将instruction和input一起作为输入序列的开头,模型被训练来生成output序列。损失函数计算模型输出与真实output的交叉熵,忽略输入部分的位置。 2. 解释原因:这样做的原因是模型学习基于上下文生成响应,而不是从头开始生成文本。 3. 引用支持:从引用[4],LLaMA-Factory使用Alpaca格式,其中输入格式化后,模型预测output。 4. 举例:如果input不为空,例如在医疗诊断中,input是上下文信息,output是诊断结果;模型输入instruction和input,生成output。 5. 总结:所以,是输入input得到output计算损失,而不是将input和output一起输入让文本做扩写。 具体来说:第二种方式描述中“将input和output一起输入让文本做扩写”可能误解了;标准做法是输入包括instruction和input的部分,目标是output,损失只在output计算。 在训练脚本中,输入序列是“instruction: ... input: ... response: ”然后output token序列,损失在response部分。 所以,用户的问题中,第一种方式更准确:输入input(含义是上下文),得到output计算损失。 结束回答时,添加引用。 然后生成相关问题。 相关问题:基于主题,如微调方法、损失计算、具体实现等。 例如: 1. 微调DeepSeek-7B时如何计算损失函数? 2. Alpaca数据格式在指令微调中的作用是什么? 3. QLoRA技术在微调中如何影响内存使用? 确保回答真实可靠。 最终回答结构: - 开头直接回答问题。 - 解释细节。 - 引用证据。 - 结束并生成相关问。 </think> ### 微调 DeepSeek-7B 模型时的输入和损失计算方式 在微调 DeepSeek-7B(或其他类似大型语言模型,如 DeepSeek-R1-Distill-Qwen-7B)时,标准做法是针对监督式微调(Supervised Fine-Tuning, SFT),使用指令跟随(instruction-following)格式的数据集。具体到您的查询:**在微调过程中,是仅输入 `input` 部分让模型生成 `output` 并计算损失,还是将 `input` 和 `output` 一起输入让模型从 `input` 续写(扩写)到 `output`?** 答案是:**通常是输入 `input`(包括 `instruction` 部分)让模型生成 `output`,然后计算损失函数(如交叉熵损失)**。我将逐步解释原因和实现细节。 #### 1. **微调过程的核心逻辑** - 在监督式微调中,模型的训练基于“上下文生成响应”的模式: - **输入序列**:由 `instruction` 和 `input` 拼接而成,作为上下文(prompt)。例如,引用[4]中提到的 Alpaca 数据格式(`{ "instruction": "...", "input": "...", "output": "..." }`),输入序列通常被格式化为字符串,如: ``` "Instruction: {instruction}\nInput: {input}\nResponse: " ``` 其中 `instruction` 是任务描述(如“医疗问题示例”),`input` 是相关信息(如“上下文信息”),`Response:` 是提示模型开始生成输出的标记[^4]。 - **目标输出序列**:是 `output` 部分(如“预期回答”)。模型被训练基于输入序列生成这个输出序列。 - **损失计算**:只针对 `output` 部分的 token 计算损失(通常是交叉熵损失)。这意味着: - 模型在训练时,输入序列包括整个上下文(`instruction + input + "Response: "`),但损失函数仅在模型生成的 token 与真实 `output` 的 token 对齐时计算。 - 输入序列的上文(如 `instruction` 和 `input`)在损失计算中被忽略或掩码(masked),以防止模型“作弊”或学习到错误的依赖性。这确保了模型只学习如何从上下文生成响应,而不是直接复制输入[^4]。 这种方法是因果语言模型(如 GPT 系列)的标准实践:模型作为自回归生成器,基于前缀预测下一个 token。因此,它不是将 `input` 和 `output` 一起输入让模型做“扩写”,而是让模型基于输入上下文续写 `output`。关键在于,损失只在 `output` 部分计算,以强化模型响应生成的能力。 #### 2. **为什么不是“输入 input 和 output 一起做扩写”?** - 如果将 `input` 和 `output` 整体作为输入序列,让模型从头生成整个序列(包括 `instruction` 和 `input`),这会导致训练目标不匹配: - 模型可能会学习重构整个输入序列(包括不必要的部分),而不是专注于生成响应(`output`)。 - 例如,在引用[3]的数据集(`{ "instruction": "绳索逃生的正确打结方法是什么?", "input": "", "output": "应使用双渔人结..." }`)中,如果 `input` 为空,模型输入应为 `instruction`,然后生成 `output`;如果 `input` 非空(如引用[4]的医疗数据),输入为 `instruction + input`,生成 `output`。 - 损失函数如果覆盖整个序列,会浪费计算资源在已知上下文上,降低微调效率。标准实现只优化响应部分,确保模型学习“指令-响应”的映射[^4]。 -DeepSeek-7B 的实现中,引用[4]提到的 LLaMA-Factory 工具使用 Alpaca 格式,正是采用这种上下文生成响应的方式。训练时,输入序列被截断或填充,但损失掩码确保只有 `output` 部分贡献梯度。 #### 3. **具体实现示例** 在代码层面(如使用 Hugging Face Transformers 库),微调过程通常这样实现: - **数据准备**:将数据集转换为输入-输出对。例如,基于引用[4]的 Alpaca 格式: - 输入字符串:`instruction + "\n" + input + "\nResponse: "` - 目标字符串:`output` - **训练循环**: - 输入序列:模型接收整个字符串(上下文 + 目标前缀),但通过注意力掩码(attention mask)确保模型只能基于上下文预测 `output`。 - 损失计算:使用交叉熵损失,但位置掩码(position mask)跳过输入序列部分(即 `instruction` 和 `input`),仅在 `output` 的 token 位置计算损失。 - 数学表示为:如果输入序列长度为 $L_{\text{in}}$,输出序列长度为 $L_{\text{out}}$,总序列长度为 $L = L_{\text{in}} + L_{\text{out}}$,则损失函数为: $$ \mathcal{L} = -\sum_{t=L_{\text{in}}}^{L} \log P(y_t | y_{<t}, x) $$ 其中 $x$ 是输入上下文,$y_t$ 是目标 token[^4]。 - **工具支持**:在 QLoRA 微调(引用[1])中,这种方法是高效的,因为它减少了内存占用,只计算必要的梯度。 #### 4. **总结和建议** - **回答您的核心问题**:在微调 DeepSeek-7B 时,是**输入 `input`(包含 `instruction`)得到 `output` 计算损失**。不是将 `input` 和 `output` 一起输入让模型做扩写;而是输入上下文后,让模型续写响应,损失只针对响应部分。 - **为什么可靠?** 这基于指令微调的最佳实践,如 Alpaca 格式的使用(引用[4]),以及因果语言模型的标准训练协议。QLoRA(引用[1])等技术优化了此过程,但核心逻辑不变。 - **注意事项**:如果数据中 `input` 为空(如引用[3]),则输入仅为 `instruction`;否则合并两者。确保数据集格式正确,以避免训练偏差。 如果您有具体代码或数据集示例,我可以帮助进一步分析! ### 相关问题 基于 DeepSeek-7B 微调的主题,以下是几个相关问题: 1. 在监督式微调中,如何设计损失函数以忽略输入序列的部分? 2. QLoRA 技术如何在微调 DeepSeek-7B 时减少内存占用?[^1] 3. 指令微调(如 Alpaca 格式)相比普通文本生成微调有哪些优势?[^4]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值