动手试了Qwen3-1.7B微调,金融问答项目完整复现分享
最近在研究如何让大模型更精准地处理垂直领域的任务,比如金融场景下的专业问答。我选择了阿里巴巴开源的Qwen3-1.7B模型进行 LoRA 微调,并成功复现了一个金融领域的问题回答系统。整个过程从环境搭建、数据预处理到训练推理,我都做了详细记录,今天就来和大家分享这个项目的完整实践路径。
如果你也想用轻量级方式让通用大模型“变身”为某个行业的专家助手,这篇文章会给你提供一条清晰可落地的技术路线。
1. 项目背景与目标
为什么选择 Qwen3-1.7B?
Qwen3 是阿里通义实验室于2025年4月推出的新一代大语言模型系列,覆盖从0.6B到235B不同参数规模。其中Qwen3-1.7B是一个非常适合本地部署和微调的小尺寸密集模型,它具备以下优势:
- 参数适中:适合单卡甚至消费级显卡运行
- 中文理解强:原生支持中文,对金融术语有良好基础理解
- 开源开放:Hugging Face 可直接下载,社区活跃
- 支持高效微调:兼容 LoRA、QLoRA 等主流方法
我们的目标是:通过少量高质量金融问答数据,对 Qwen3-1.7B 进行微调,使其能够根据上下文信息准确回答专业问题,且输出简洁规范。
2. 数据准备与处理
2.1 数据集来源
我们使用的数据来自 GitHub 上公开的 RAG 教学项目:
https://raw.githubusercontent.com/Steven-Luo/MasteringRAG/main/outputs/v1_1_20240811/question_answer.xlsx该数据集包含多个领域的问答对,但我们只提取dataset == 'train'且含有context字段的样本,聚焦于金融类问题。
2.2 构建指令模板
为了让模型学会“看材料答题”,我们需要构造类似如下格式的 prompt:
你是一个金融分析师,擅长根据所获取的信息片段,对问题进行分析和推理。 你的任务是根据所获取的信息片段(<context></context>之间的内容)回答问题。 回答保持简洁,不必重复问题,不要添加描述性解释和与答案无关的任何内容。 已知信息: <context> [此处插入背景文本] </context> 问题: [此处插入问题] 请回答:为此编写了build_sample函数:
def build_sample(row): prompt = """ 你是一个金融分析师,擅长根据所获取的信息片段,对问题进行分析和推理。 你的任务是根据所获取的信息片段(<context></context>之间的内容)回答问题。 回答保持简洁,不必重复问题,不要添加描述性解释和与答案无关的任何内容。 已知信息: <context> {context} </context> 问题: {question} 请回答:/no_think""".format(context=row['context'], question=row['question']).strip() return prompt注意末尾加/no_think是为了配合后续推理时关闭思维链功能。
2.3 输出格式统一
我们将标准答案包装成<think>\n</think>+ 答案的形式,这样即使开启思维链模式,也能确保最终输出结构一致:
df['output'] = df['answer'].apply(lambda x: '<think>\n</think>' + x)2.4 转换为对话格式
使用 Hugging Face 的datasets库将数据转换为标准对话结构:
from datasets import Dataset rag_dataset = Dataset.from_pandas(df[['instruction', 'output']]) def generate_conversation(examples): problems = examples["instruction"] solutions = examples["output"] conversations = [] for problem, solution in zip(problems, solutions): conversations.append([ {"role": "user", "content": problem}, {"role": "assistant", "content": solution} ]) return {"conversations": conversations} # 注意:需要先加载 tokenizer rag_dataset_conversation = tokenizer.apply_chat_template( rag_dataset.map(generate_conversation, batched=True)["conversations"], tokenize=False, ) train_dataset = Dataset.from_pandas(pd.DataFrame({'text': rag_dataset_conversation})) train_dataset.name = 'text'处理后的样例如下:
[ { "role": "user", "content": "你是一个金融分析师...\n请回答:/no_think" }, { "role": "assistant", "content": "<think>\n</think>2023年全球经济增长动力持续回落..." } ]3. 环境配置与模型加载
3.1 安装依赖库
为了实现高效微调,我们采用Unsloth + PEFT + Transformers组合方案,兼顾速度与稳定性。
pip install --no-deps bitsandbytes accelerate xformers==0.0.29.post3 peft trl==0.15.2 triton cut_cross_entropy unsloth_zoo pip install sentencepiece protobuf "datasets>=3.4.1" huggingface_hub hf_transfer pip install transformers==4.51.3 pip install --no-deps unsloth主要组件说明:
| 工具 | 作用 |
|---|---|
transformers | 模型架构与推理核心 |
peft | LoRA 微调支持 |
trl | SFTTrainer 训练框架 |
unsloth | 加速 LoRA 训练,降低显存占用 |
bitsandbytes | 4-bit 量化支持 |
accelerate | 分布式与混合精度训练 |
3.2 下载基础模型
git clone https://huggingface.co/Qwen/Qwen3-1.7B模型文件较大(约3.5GB),建议在网络稳定环境下拉取。
3.3 使用 Unsloth 加载模型并启用 LoRA
from unsloth import FastLanguageModel import torch model, tokenizer = FastLanguageModel.from_pretrained( model_name="/kaggle/working/Qwen3-1.7B", max_seq_length=4096, load_in_4bit=True, # 启用4bit量化 load_in_8bit=False, full_finetuning=False, # 使用LoRA而非全参数微调 ) # 配置LoRA参数 model = FastLanguageModel.get_peft_model( model, r=32, 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, )这里的关键点:
r=32表示 LoRA 秩数,越大拟合能力越强但显存消耗更高target_modules明确指定要注入 LoRA 的注意力层和前馈层use_gradient_checkpointing="unsloth"可显著减少长序列训练时的显存占用
4. 训练过程详解
4.1 设置训练参数
我们使用 TRL 提供的SFTTrainer进行监督微调:
from trl import SFTTrainer, SFTConfig trainer = SFTTrainer( model=model, tokenizer=tokenizer, train_dataset=train_dataset, args=SFTConfig( dataset_text_field="text", per_device_train_batch_size=2, gradient_accumulation_steps=4, # 等效 batch size = 8 warmup_steps=5, max_steps=200, # 小样本快速验证 learning_rate=2e-4, logging_steps=1, optim="adamw_8bit", weight_decay=0.01, lr_scheduler_type="cosine", seed=3407, report_to="none", # 不连接W&B等平台 ) )关键参数解读:
per_device_train_batch_size=2:每张卡处理2个样本,防止OOMgradient_accumulation_steps=4:累积梯度模拟更大batchmax_steps=200:初步实验控制训练步数,避免过拟合learning_rate=2e-4:适用于 LoRA 微调的典型学习率
4.2 显存优化技巧
在训练过程中,我发现显存容易碎片化导致 OOM。解决办法是在启动前设置环境变量:
export PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True,max_split_size_mb:128这能有效减少内存碎片,提升 GPU 利用效率,尤其在长时间训练或长文本场景下效果明显。
5. 模型保存与合并
5.1 保存 LoRA 权重
训练完成后,先本地保存 LoRA 增量权重:
version = "1.0" model.save_pretrained("lora_model") tokenizer.save_pretrained("lora_model")这部分体积小(约几十MB),便于调试和版本管理。
5.2 合并为完整模型
生产部署通常需要将 LoRA 权重合并回原始模型,生成独立可用的 checkpoint:
model.save_pretrained_merged(f"model_{version}", tokenizer, save_method="merged_16bit")save_method="merged_16bit"表示以 FP16 格式保存合并后的模型,平衡精度与体积。
6. 推送到 Hugging Face Hub
我们可以将合并后的模型推送到自己的 HF 账号,方便后续调用或分享:
try: model.push_to_hub_merged( repo_id="fengn/qwen3", tokenizer=tokenizer, save_method="merged_16bit", token="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" ) print("成功推送合并模型") except Exception as e: print(f"合并推送失败: {e}") print("尝试标准推送方法...") model.push_to_hub("fengn/qwen3", token="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") tokenizer.push_to_hub("fengn/qwen3", token="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") print("成功推送标准模型")推送成功后,其他人就可以通过AutoModelForCausalLM.from_pretrained("fengn/qwen3")直接加载你的微调成果。
7. 推理测试与效果评估
7.1 加载合并模型
import torch import gc from transformers import AutoModelForCausalLM, AutoTokenizer torch.cuda.empty_cache() gc.collect() device = "cuda" if torch.cuda.is_available() else "cpu" model_path = "model_1.0" tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True) model = AutoModelForCausalLM.from_pretrained( model_path, torch_dtype=torch.float16 if device == "cuda" else torch.float32, trust_remote_code=True, low_cpu_mem_usage=True ).to(device)7.2 编写推理函数
def inference_with_context(context, question, model, tokenizer): prompt = f""" 你是一个金融分析师,擅长根据所获取的信息片段,对问题进行分析和推理。 你的任务是根据所获取的信息片段(<context></context>之间的内容)回答问题。 回答保持简洁,不必重复问题,不要添加描述性解释和与答案无关的任何内容。 已知信息: <context> {context} </context> 问题: {question} 请回答:/no_think""" inputs = tokenizer(prompt, return_tensors="pt").to(device) outputs = model.generate(**inputs, max_new_tokens=200, temperature=0.5) response = tokenizer.decode(outputs[0], skip_special_tokens=True) # 提取回答部分(去除prompt) answer_start = response.find("请回答:") + len("请回答:") final_answer = response[answer_start:].replace("/no_think", "").strip() return final_answer7.3 实际测试案例
输入上下文:
某科技公司2023年第三季度财报显示:
- 营业收入:120亿元,同比增长25%
- 净利润:18亿元,同比增长30%
- 研发投入:15亿元,占营收的12.5%
- 现金流:净流入8亿元
- 主要业务:云计算服务、人工智能解决方案
提问:
基于这些财务数据,该公司的盈利能力和成长性如何?
模型输出:
该公司盈利能力较强,净利润率达15%;成长性良好,营收和利润均保持高速增长,研发投入占比高,具备长期竞争力。
结果表明,模型不仅能提取关键指标,还能做出合理判断,达到了预期的专业分析水平。
8. 总结与经验分享
这次对 Qwen3-1.7B 的微调实践证明,即使是1.7B级别的小模型,只要经过合适的领域数据训练,也能胜任专业金融问答任务。以下是几点关键收获:
- 数据质量比数量更重要:我们仅用了几百条高质量标注数据,就实现了不错的泛化能力。
- Unsloth 极大提升了训练效率:相比传统 LoRA 实现,训练速度提升约30%,显存占用更低。
- 指令设计决定行为模式:清晰的角色设定和输出约束能让模型表现更可控。
- 4-bit 量化完全可用:在微调阶段即可启用,大幅降低硬件门槛。
- 合并模型更适合部署:虽然 LoRA 轻便,但生产环境推荐使用合并后的完整模型,避免依赖加载逻辑。
如果你想快速构建一个垂直领域的智能问答系统,这套流程完全可以复用——只需替换数据集和提示词模板,就能打造属于你自己的“行业专家”。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。