欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 新闻 > 资讯 > 一次使用 RAFT 和 Qwen3 实现端到端领域RAG自适应

一次使用 RAFT 和 Qwen3 实现端到端领域RAG自适应

2025/6/23 7:24:11 来源:https://blog.csdn.net/qq_36603091/article/details/148828649  浏览:    关键词:一次使用 RAFT 和 Qwen3 实现端到端领域RAG自适应

最近,我参与了一个项目,其目标是使用特定于领域的数据为客户微调一个小语言模型。在探索了一些方法之后,我决定使用 RAFT(检索增强微调),以充分利用检索和监督学习。

为此,我使用 unsloth 对 qwen3-4b 模型进行了微调。工作流程非常简单 — 首先,我从客户的文件中生成训练数据,然后对其进行清理和结构化,应用聊天风格的模板,最后开始微调过程。

我们最终得到了很好的结果——尤其是在微调了该领域的上下文嵌入模型以及与之相关的 Re-ranker 之后。我在之前的文章中已经介绍了这些部分,我将在这篇文章的末尾链接到它们。在这里,重点是事物的生成方面 — 我们如何构建和塑造数据,以实际教会模型需要了解的内容。

RAFT:使语言模型适应特定领域的 RAG

None

RAFT — 检索增强微调的缩写 — 是一种将检索增强生成与监督微调相结合的方法。Raft 不仅在静态数据上训练模型,还在训练过程中引入相关文档(有时是不相关的文档),帮助模型学习如何在实际上下文中构建答案。这不仅提高了准确性,还减少了幻觉,尤其是在特定领域的任务中。这就像教模型用手里的笔记进行推理 - 并知道何时信任它们。

Qwen3

None

Qwen3 是阿里巴巴推出的最新一代开源语言模型,从 0.6b 和 4b 等紧凑版本一直到具有 235b 参数的大规模 moes。它引入了混合推理设计,让模型在“思考”模式(用于数学、编码或逻辑等复杂任务)和“非思考”模式之间动态切换,以实现更快、更一般的响应。除了推理之外,Qwen3 还支持长上下文窗口(最多 ~128K 个令牌)、跨 119 种语言的多语言理解,以及密集和混合专家架构——这是使用 peft、TRL、Unsloth 等系统进行微调的强大基础…

Unsloth

None

Unsloth 是一个开源微调库,可以更快、更高效地适应 LLM。通过将关键的 PyTorch作替换为自定义的 Triton GPU 内核,它可以将训练速度提高大约 2×,并将内存使用量减少多达 70%,所有这些都不会降低准确性。

它与 Hugging Face、peft 和 trl 工作流程完全兼容,在从入门级 T4 到高端 H100 的 Nvidia GPU 上支持 LoRA/QLoRA,即使在免费的 Colab/Kaggle 环境中也能流畅运行。速度、效率和开源灵活性的结合使 Unsloth 非常适合 RAFT 和 qwen3 微调等更多实验性管道。

代码时间 :

安装依赖项

我们首先使用 llama-indexunsloth 和支持包安装所有必需的库以进行微调和检索。

%%capture
!pip install llama-index
!pip install llama-index-packs-raft-dataset
!pip install llama-index-llms-google-genai llama-index-embeddings-google-genai
%%capture
import os
if "COLAB_" not in "".join(os.environ.keys()):!pip install unsloth
else:# Do this only in Colab notebooks! Otherwise use pip install unsloth!pip install --no-deps bitsandbytes accelerate xformers==0.0.29.post3 peft trl triton cut_cross_entropy unsloth_zoo!pip install sentencepiece protobuf "datasets>=3.4.1" huggingface_hub hf_transfer!pip install --no-deps unsloth

正如我们在上面看到的,设置会根据我们是在 colab 中运行还是在本地环境中运行而自动调整 — 仅在需要时安装额外的低级依赖项。

生成 RAFT 训练数据集

在此步骤中,我们使用 llama-index 的 RAFTDatasetPack 准备数据集以进行检索增强微调。

import osfrom llama_index.packs.raft_dataset import RAFTDatasetPack
from llama_index.llms.google_genai import GoogleGenAI
from llama_index.embeddings.google_genai import GoogleGenAIEmbeddingGOOGLE_API_KEY = "xxxxxx-xxxxxxxxxx"
os.environ["GOOGLE_API_KEY"] = GOOGLE_API_KEYllm = GoogleGenAI(model="gemini-2.5-flash-preview-05-20")embed_model = GoogleGenAIEmbedding(model_name="text-embedding-004")file_path = "/path/to.txt" # or md or pdf ..raft_dataset = RAFTDatasetPack(file_path ,llm=llm ,embed_model=embed_model ,num_questions_per_chunk=3 ,num_distract_docs=3 ,chunk_size=2048)dataset = raft_dataset.run()

正如我们在上面看到的,Gemini 2.5 模型处理问题的生成,而 Google 的嵌入 API 创建向量表示。数据集是从源文件(TXT、MD 或 PDF)构建的,其中每个块都与生成的问题和干扰项文档配对,以模拟检索场景。最终输出是一个结构化数据集,可用于监督式微调。

格式化和保存数据集

生成初始 RAFT 数据集后,我们将其转换为 Hugging Face 兼容的格式。

import datasetsdf = dataset.to_pandas()# Combine 'user' and 'assistant' columns into a new 'messages' column as list of dictionaries
df['messages'] = df.apply(lambda row: [{'content': row['instruction'], 'role': 'user'},{'content': row['cot_answer'], 'role': 'assistant'}], axis=1)
dataset = datasets.Dataset.from_pandas(df)# Optional (save the dataset)output_path = "data"
dataset.save_to_disk(output_path)
dataset.to_json(output_path + ".jsonl")

Eeach 数据点被重塑为带有userassistant消息的聊天式结构,以匹配指令调整标准。最后,数据集既可以保存到磁盘,也可以保存为 .jsonl 文件,以便灵活地进行下游训练或检查。

使用 unsloth 加载基本模型

在这里,我们使用 unsloth 优化的 FastLanguageModel 包装器加载 qwen3-4b-128k 模型。

from unsloth import FastLanguageModel
import torchmodel, tokenizer = FastLanguageModel.from_pretrained(model_name = "unsloth/Qwen3-4B-128K",max_seq_length =4096 ,   # Context length - can be longer, but uses more memoryload_in_4bit = True,     # 4bit uses much less memoryload_in_8bit = False,    # A bit more accurate, uses 2x memoryfull_finetuning = False, # We have full finetuning now!# token = "hf_...",      # use one if using gated models
)
import re
import random
from multiprocessing import cpu_count# Set chat template
DEFAULT_CHAT_TEMPLATE = "{% for message in messages %}\n{% if message['role'] == 'user' %}\n{{ '<|user|>\n' + message['content'] + eos_token }}\n{% elif message['role'] == 'system' %}\n{{ '<|system|>\n' + message['content'] + eos_token }}\n{% elif message['role'] == 'assistant' %}\n{{ '<|assistant|>\n'  + message['content'] + eos_token }}\n{% endif %}\n{% if loop.last and add_generation_prompt %}\n{{ '<|assistant|>' }}\n{% endif %}\n{% endfor %}"
tokenizer.chat_template = DEFAULT_CHAT_TEMPLATESYSTEM_PROMPT = """
You are a helpful question answerer who can provide an answer given a question and relevant context.
"""def apply_chat_template(example, tokenizer):messages = example["messages"]# We add an empty system message if there is noneif messages[0]["role"] != "system":messages.insert(0, {"role": "system", "content": SYSTEM_PROMPT})example["text"] = tokenizer.apply_chat_template(messages, tokenize=False)return examplecolumn_names = list(dataset.features)
raw_datasets = dataset.map(apply_chat_template,num_proc=cpu_count(),fn_kwargs={"tokenizer": tokenizer},remove_columns=column_names,desc="Applying chat template",)

正如我们之前看到的,每次交互都包括一条user消息和一个assistant响应——在这里,我们还插入了一个system提示来指导助手的行为。最终输出是一个使用 tokenizer.apply_chat_template 构建的干净、模型就绪的文本字段,使用所有可用的 CPU 内核高效并行应用。

使用 SFTTrainer 配置 LoRA 微调

在本节中,我们使用 unsloth 和 trlSFTTrainer 设置基于 LoRA 的微调。

from trl import SFTTrainer, SFTConfigraw_datasets = raw_datasets.train_test_split(test_size=0.1)
# create the splits
train_dataset = raw_datasets["train"]
eval_dataset = raw_datasets["test"]model = FastLanguageModel.get_peft_model(model,r = 32,           # Choose any number > 0! Suggested 8, 16, 32, 64, 128target_modules = ["q_proj", "k_proj", "v_proj", "o_proj","gate_proj", "up_proj", "down_proj",],lora_alpha = 32,  # Best to choose alpha = rank or rank*2lora_dropout = 0, # Supports any, but = 0 is optimizedbias = "none",    # Supports any, but = "none" is optimized# [NEW] "unsloth" uses 30% less VRAM, fits 2x larger batch sizes!use_gradient_checkpointing = "unsloth", # True or "unsloth" for very long contextrandom_state = 3407,use_rslora = False,   # We support rank stabilized LoRAloftq_config = None,  # And LoftQ
)trainer = SFTTrainer(model = model,train_dataset = train_dataset,eval_dataset = eval_dataset,args = SFTConfig(dataset_text_field = "text",per_device_train_batch_size = 2,gradient_accumulation_steps = 4, # Use GA to mimic batch size!warmup_steps = 5,# num_train_epochs = 1, # Set this for 1 full training run.max_steps = 30,learning_rate = 2e-4, # Reduce to 2e-5 for long training runslogging_steps = 1,optim = "adamw_8bit",weight_decay = 0.01,lr_scheduler_type = "linear",seed = 3407,report_to = "none", # Use this for WandB etc),
)tokenizer.pad_token = tokenizer.eos_token

该模型使用 get_peft_model 包装,以关键投影层为目标,并使用低秩自适应策略来减少训练开销。我们将数据集分为 Train 和 Test,然后为 Trainer 配置适度的训练步骤、梯度累积和 8 位优化以提高效率。使用 Unsloth 的优化后端启用梯度检查点,以最少的内存使用量支持更长的序列。这种设置平衡了速度、稳定性和对特定领域数据的适应性。

启动微调过程
train_result = trainer.train()

最后,我们使用 trainer.train() 开始监督微调。正如我们之前设置的所有内容(从数据准备到模型优化)一样,此步骤将运行实际的训练循环,使用 LoRA 配置和我们的格式化数据集将基本 Qwen3 模型适应目标域。结果是一个更能感知上下文、更高效的模型,针对特定于域的任务进行了优化。

测试微调模型

作为最后一步,我们使用结构化提示(包括系统指南和用户查询)测试微调后的模型。

messages = [{"role" : "system", "content" : SYSTEM_PROMPT},{"role" : "user", "content" : "<Context> + Question"}
]
text = tokenizer.apply_chat_template(messages,tokenize = False,add_generation_prompt = True, # Must add for generationenable_thinking = False, # Disable thinking
)from transformers import TextStreamer
_ = model.generate(**tokenizer(text, return_tensors = "pt").to("cuda"),max_new_tokens = 256, # Increase for longer outputs!temperature = 0.7, top_p = 0.8, top_k = 20, # For non thinkingstreamer = TextStreamer(tokenizer, skip_prompt = True),
)

我们重复使用聊天模板并设置 add_generation_prompt=True 以触发生成。禁用思考模式以获得更快、更简单的响应。使用 TextStreamer 实时流式传输输出,使我们能够快速验证模型在特定于域的输入上的行为。

保存和重新加载微调的模型

训练后,我们将模型和 Tokenizer 都保存在本地的 "lora_model" 下以备将来使用。

model.save_pretrained("lora_model")  # Local saving
tokenizer.save_pretrained("lora_model")
# model.push_to_hub("your_name/lora_model", token = "...") # Online saving
# tokenizer.push_to_hub("your_name/lora_model", token = "...") # Online saving# Now if you want to load the LoRA adapters we just saved for inference, set False to True:if False:from unsloth import FastLanguageModelmodel, tokenizer = FastLanguageModel.from_pretrained(model_name = "lora_model", # YOUR MODEL YOU USED FOR TRAININGmax_seq_length = 2048,load_in_4bit = True,)

或者,可以将相同的对象推送到 Hugging Face Hub 进行共享或部署。如上所示,重新加载保存的 LoRA 适配器非常简单 — 只需将 FastLanguageModel.from_pretrained 指向保存的目录并重新启用 4 位加载即可。这可确保模型可移植,并准备好进行推理或进一步调整。

导出模型以进行部署
# Merge to 16bit
if False:model.save_pretrained_merged("model", tokenizer, save_method = "merged_16bit",)
if False: # Pushing to HF Hubmodel.push_to_hub_merged("hf/model", tokenizer, save_method = "merged_16bit", token = "")# Merge to 4bit
if False:model.save_pretrained_merged("model", tokenizer, save_method = "merged_4bit",)
if False: # Pushing to HF Hubmodel.push_to_hub_merged("hf/model", tokenizer, save_method = "merged_4bit", token = "")# Just LoRA adapters
if False:model.save_pretrained_merged("model", tokenizer, save_method = "lora",)
if False: # Pushing to HF Hubmodel.push_to_hub_merged("hf/model", tokenizer, save_method = "lora", token = "")

Unsloth 支持以多种格式保存模型以进行部署:

  • 使用 merged_16bitmerged_4bit 进行 float16/int4 导出
  • 仅使用 save_method="lora" 保存 LoRA 适配器
# Save to 8bit Q8_0
if False:model.save_pretrained_gguf("model", tokenizer,)
# Remember to go to https://huggingface.co/settings/tokens for a token!
# And change hf to your username!
if False:model.push_to_hub_gguf("hf/model", tokenizer, token = "")# Save to 16bit GGUF
if False:model.save_pretrained_gguf("model", tokenizer, quantization_method = "f16")
if False: # Pushing to HF Hubmodel.push_to_hub_gguf("hf/model", tokenizer, quantization_method = "f16", token = "")# Save to q4_k_m GGUF
if False:model.save_pretrained_gguf("model", tokenizer, quantization_method = "q4_k_m")
if False: # Pushing to HF Hubmodel.push_to_hub_gguf("hf/model", tokenizer, quantization_method = "q4_k_m", token = "")# Save to multiple GGUF options - much faster if you want multiple!
if False:model.push_to_hub_gguf("hf/model", # Change hf to your username!tokenizer,quantization_method = ["q4_k_m", "q8_0", "q5_k_m",],token = "", # Get a token at https://huggingface.co/settings/tokens)
  • 对于llama.cpp,请使用 q4_k_mq5_k_mq8_0 等定量方法导出到 GGUF

本文介绍了使用 RAFT 方法微调小型语言模型的端到端过程。我们首先使用 llama-index 和 AI生成结构化训练数据,应用聊天风格的格式,并使用 Unsloth 微调 Qwen3 模型。整个目标是保持高效并针对域进行定制。我们还研究了如何保存和导出模型以进行部署。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词