
在我们的印象中,我们曾比喻大模型为一位学识渊博、通晓古今的大学者,他读过亿万本书,知识储备深不可测。但是,如果我们先想让他成为我们公司的法律顾问,或者帮我们写中医养生文案,他可能会显得博而不专。如果直接让他去背法律条文或中医典籍,做一个全参数微调,这就像让这位大学者为了一个新专业,把他过去学过的所有知识都重新梳理、更新一遍,这个过程不仅耗时耗力,也需要巨大的算力和时间,而且成本极高,甚至可能因为学习新知识而干扰到他原有的广博学识,形成灾难性遗忘。
那么,有没有一种方法,能像给这位学者配一个专业速成笔记本一样,让他快速掌握新技能,同时又不用动他脑子里的底层知识呢?明确的说,是有的,这就是我们今天要介绍的LoRA技术。它是一种 “轻量级微调” 或 “高效参数微调” 方法,能让我们用极低的成本,为庞大的AI模型注入专属技能。

LoRA,英文全称是 Low-Rank Adaptation of Large Language Models,中文翻译为 “大语言模型的低秩自适应”。这个名字听起来很复杂,但我们把它拆开看:
简单来说,LoRA就是一种用小改动实现大效果的模型微调技术。
简单明了的标识就是:给模型穿上一件特殊的技能马甲,让我们用一个更生活化的比喻来理解:
这些技能马甲就是LoRA训练出来的小文件,通常只有几MB到几百MB,而原始的T恤(基础模型)往往有几十GB。 这就是LoRA在存储和切换上的巨大优势。
AI模型的核心是无数个权重矩阵,我们可以把它们想象成模型大脑中负责决策的“神经突触连接强度”。在微调时,我们希望调整这些连接强度,让模型在新任务上表现更好。在LoRA出现之前,微调大型模型的主流方法是全参数微调。这意味着需要更新模型所有权重,可能包含数十亿甚至数千亿参数,这带来了巨大的计算、存储和硬件成本。
LoRA的核心思想基于一个关键的假设:模型在适应新任务时,其权重变化具有“低内在秩”。尽管模型本身非常庞大,高维,但当它学习一个新任务时,其需要做出的“改变量”其实是可以用一个低维度的结构来有效表示的。简单来说,尽管模型的权重矩阵很大(例如 4096 x 4096),但其在任务适配过程中的变化量(ΔW)可以用一个低维度的矩阵来有效表示。
通俗的理解:
制作一个复杂的乐高城市沙盘需要成千上万个积木,此沙盘好比我们的基础模型。现在,我们需要把这个沙盘从现代都市改造成科幻未来城。我们不需要把每一个积木都拆掉重拼,也就是不需要进行全参数微调。观察后发现,大部分改变都集中在给大楼加上霓虹灯、给街道换上磁悬浮轨道、给天空加上飞行器这几个核心维度上。你只需要设计一套科幻改造套件,这个套件就可以理解为低秩矩阵,直接附加在原有的沙盘上,就能实现目标。
在技术上,首先对比一下传统微调和LoRA微调的差异行性:
换个方式说明:
想象一个预训练好的权重矩阵 W₀(维度为 d x k)。在微调过程中,它的更新是 ΔW。全参数微调直接学习 ΔW(也是一个 d x k 的大矩阵)。
LoRA的做法是:
直观理解:
与其直接学习一个巨大的 ΔW(有 d*k 个参数),LoRA只学习两个小矩阵 A 和 B(共有 d*r+ r*k 个参数)。因为 r 很小,所以需要训练的参数数量减少了几个数量级。
举例: 对于一个 1024x1024 的权重矩阵,全微调需要学习 1,048,576 个参数。如果使用 r=8 的LoRA,只需要学习 (1024*8) + (8*1024) = 16,384 个参数,仅为原来的 1.56%。
这个流程图清晰地展示了LoRA如何通过引入两个小矩阵A和B,在不改变原始权重W的情况下实现对模型的高效微调。

在图中体现的路径解析:
LoRA的可视化示例 :
假设原始权重矩阵 W 是一个 1000x1000 的庞然大物,有一百万个参数。
通过训练这极少量的参数,LoRA就能捕捉到模型适应新任务所需的核心变化,实现高效微调。
lora_config = LoraConfig(
r=8, # 秩的大小
# ... 其他参数
)# 不同秩值的参数数量对比
def calculate_lora_params(base_params, r, target_modules_count):
"""
计算LoRA参数数量
base_params: 基础模型参数量
target_modules_count: 目标模块的数量
"""
lora_params = 0
for module in target_modules:
# 假设每个目标模块的维度是 d x d
lora_params += 2 * d * r # A矩阵: d×r, B矩阵: r×d
return lora_params
# 示例:对于Qwen1.5-0.5B,不同秩的参数对比
rank_comparison = {
"r=2": {"params": 196,608, "percentage": 0.035%},
"r=4": {"params": 393,216, "percentage": 0.070%},
"r=8": {"params": 786,432, "percentage": 0.140%},
"r=16": {"params": 1,572,864, "percentage": 0.281%},
"r=32": {"params": 3,145,728, "percentage": 0.563%}
}任务类型 | 推荐秩 | 说明 |
|---|---|---|
简单任务(风格迁移) | 2-8 | 任务简单,小秩足够 |
中等任务(领域适配) | 8-16 | 需要一定表达能力 |
复杂任务(推理任务) | 16-32 | 需要较强的特征组合能力 |
实验调试 | 从8开始 | 平衡效果和效率 |
lora_config = LoraConfig(
r=8,
lora_alpha=16, # 缩放因子
# ...
)# alpha/r 的实际影响
def lora_contribution(alpha, r):
scale = alpha / r
return scale
# 示例对比
scale_examples = {
"r=8, alpha=16": lora_contribution(16, 8), # scale=2.0
"r=8, alpha=8": lora_contribution(8, 8), # scale=1.0
"r=8, alpha=4": lora_contribution(4, 8), # scale=0.5
"r=16, alpha=32": lora_contribution(32, 16), # scale=2.0
}q_proj、v_proj、k_proj、o_proj、gate_proj、up_proj、down_proj、lm_head 这些是Transformer模型中的关键组件名称。让我用通俗易懂的方式详细介绍:
q_proj, k_proj, v_proj - 查询、键、值投影:
# 在注意力机制中的作用
Q = q_proj(hidden_states) # 查询向量:我要找什么?
K = k_proj(hidden_states) # 键向量:我有什么?
V = v_proj(hidden_states) # 值向量:我的内容是什么?通俗理解:
比喻:当我们访问搜索引擎时,查询内容:
o_proj - 输出投影:
# 注意力机制的最后一步
attention_output = attention_weights @ V
final_output = o_proj(attention_output) # 投影回原始维度作用: 把注意力机制的输出转换回合适的维度,准备进入下一层。
gate_proj, up_proj, down_proj - 门控前馈网络:
# SwiGLU激活函数的前馈网络
gate = gate_proj(hidden_states) # 门控信号
up = up_proj(hidden_states) # 上投影
down = down_proj(gate * up) # 下投影
# 相当于:down_proj(gate_proj(x) * up_proj(x))通俗理解:
比喻:智能过滤器
lm_head - 语言模型头:
# 模型的最后输出层
hidden_states = ... # 经过所有层处理后的隐藏状态
logits = lm_head(hidden_states) # 转换为词汇表概率
next_token = argmax(logits) # 选择最可能的下一个token比喻:翻译官
作用: 把模型的内部表示转换成具体的词汇概率分布。
lora_config = LoraConfig(
target_modules=["q_proj", "v_proj", "k_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],
# ...
)
模块名称 | 所属组件 | 作用 |
|---|---|---|
q_proj, k_proj, v_proj | 注意力机制 | 查询、键、值投影 |
o_proj | 注意力机制 | 输出投影 |
gate_proj, up_proj, down_proj | FFN层 | 门控、上、下投影 |
lm_head | 输出层 | 语言模型头 |
# 策略1:只调注意力机制(最常用)
target_modules1 = ["q_proj", "k_proj", "v_proj", "o_proj"]
# 策略2:注意力+FFN(更全面)
target_modules2 = ["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"]
# 策略3:只调Q、V(参数最少)
target_modules3 = ["q_proj", "v_proj"]这些组件的作用可以简单记忆:
在LoRA微调中,优先调整 q_proj 和 v_proj 通常就能获得很好的效果,因为它们直接控制了模型的注意力机制
lora_config = LoraConfig(
lora_dropout=0.05, # Dropout比率
# ...
)dropout_choices = {
"小数据集(<1000样本)": 0.05-0.1, # 容易过拟合,需要更多正则化
"中等数据集(1000-10000)": 0.05, # 平衡状态
"大数据集(>10000)": 0.0-0.05, # 不容易过拟合,可以少用或不用
"任务简单": 0.0-0.05, # 任务简单,减少正则化
"任务复杂": 0.05-0.1, # 任务复杂,需要更多正则化
}lora_config = LoraConfig(
bias="none", # 可选:"none", "all", "lora_only"
# ...
)lora_config_simple = LoraConfig(
r=4, # 小秩,任务简单
lora_alpha=8, # 适中的缩放
target_modules=["q_proj", "v_proj"], # 只调关键模块
lora_dropout=0.05, # 轻微正则化
bias="none", # 不训练偏置
task_type="CAUSAL_LM"
)lora_config_complex = LoraConfig(
r=16, # 较大秩,任务复杂
lora_alpha=32, # 较大的影响因子
target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],
lora_dropout=0.1, # 较强的正则化
bias="none",
task_type="CAUSAL_LM"
)lora_config_minimal = LoraConfig(
r=2, # 最小秩
lora_alpha=4, # 小缩放
target_modules=["q_proj"], # 只调查询投影
lora_dropout=0.0, # 无dropout
bias="none",
task_type="CAUSAL_LM"
)LoRA微调的成功关键在于合理的参数配置:
黄金法则:从小开始,逐步调整,根据实际效果优化。 通过理解每个参数的作用和相互影响,就能高效地使用LoRA技术来定制自己的AI模型。
通过微调Qwen1.5-0.5B模型生成小红书风格的文案!
from modelscope import AutoModelForCausalLM, AutoTokenizer, snapshot_download
from peft import LoraConfig, get_peft_model
import torch
# 设置设备为CPU
device = "cpu"
# 从ModelScope下载/加载模型和分词器
model_id = "qwen/Qwen1.5-0.5B"
cache_dir = "D:\\modelscope\\hub"
print("正在下载/校验模型缓存...")
local_model_path = snapshot_download(model_id, cache_dir=cache_dir)
print("正在加载模型...")
tokenizer = AutoTokenizer.from_pretrained(local_model_path, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(
local_model_path,
trust_remote_code=True,
torch_dtype=torch.float32, # CPU上使用float32
device_map=device
)
# 设置padding token
if tokenizer.pad_token is None:
tokenizer.pad_token = tokenizer.eos_token
print("模型加载完成!")
# 生成微调前的基线输出用于对比
print("开始生成微调前基线输出...")
baseline_prompt = "### 指令:\n写一篇关于‘秋天的第一杯奶茶’的小红书文案\n\n### 回答:\n"
baseline_inputs = tokenizer(baseline_prompt, return_tensors="pt").to(model.device)
with torch.no_grad():
baseline_outputs = model.generate(
**baseline_inputs,
max_new_tokens=200,
temperature=0.8,
do_sample=True,
top_p=0.9,
)
baseline_text = tokenizer.decode(baseline_outputs[0], skip_special_tokens=True)
print("【微调前】", baseline_text)
from peft import LoraConfig, get_peft_model
# 这就是LoRA的配置“蓝图”
lora_config = LoraConfig(
r=16, # 秩,可以理解为“技能马甲”的精细度。通常8,16,32,越大越精细但也可能过拟合。
lora_alpha=32, # 一个缩放因子,通常设为r的2倍左右。
target_modules=["q_proj", "v_proj", "k_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],
# 以上指定了要把“技能徽章”贴到模型的哪些部位。通常是注意力机制和全连接层。
lora_dropout=0.05, # 随机丢弃一些神经元,防止过拟合,让模型更鲁棒。
bias="none",
task_type="CAUSAL_LM" # 指定我们的任务是因果语言模型(即文本生成)。
)
# 将基础模型“包装”成LoRA模型
model = get_peft_model(model, lora_config)
model.print_trainable_parameters() # 打印可训练参数数量
from datasets import Dataset
# 模拟一些训练数据:指令-输出对
data = [
{
"instruction": "写一篇关于‘周末露营’的小红书文案",
"output": "救命!这个露营地也太太太出片了吧!\n\n周末逃离城市计划大成功!\n找到了一个宝藏营地,草坪超大超干净~\n傍晚的夕阳绝美,随手一拍都是大片📸\n姐妹们快冲!链接我放评论区啦~\n\n#周末去哪儿 #露营穿搭 #治愈系风景"
},
{
"instruction": "推荐一款‘办公室零食’",
"output": "办公室摸鱼零食天花板被我找到啦!\n\n这个海苔脆真的好吃到跺脚!\n卡茨卡茨根本停不下来,关键是低卡零负担!\n同事都被我安利了一圈,纷纷要链接\n\n#办公室零食 #打工人日常 #低卡零食"
},
# ... 可以准备更多这样的数据
]
# 将数据格式化成模型能理解的文本格式
def format_func(example):
text = f"### 指令:\n{example['instruction']}\n\n### 回答:\n{example['output']}"
return {"text": text}
dataset = Dataset.from_list(data).map(format_func)
# 对文本进行分词
tokenized_dataset = dataset.map(lambda x: tokenizer(x["text"], truncation=True, max_length=512), batched=True)
from transformers import TrainingArguments, Trainer, DataCollatorForLanguageModeling
training_args = TrainingArguments(
output_dir="./xiaohongshu-lora", # 输出目录
per_device_train_batch_size=4, # 根据你的GPU调整
gradient_accumulation_steps=4, # 梯度累积,模拟更大的batch size
learning_rate=1e-4, # 学习率
num_train_epochs=3, # 训练轮数
logging_steps=10,
logging_dir="./logs",
save_steps=200,
fp16=torch.cuda.is_available(), # 在支持半精度的GPU上开启,可以加速训练
)
trainer = Trainer(
model=model,
args=training_args,
train_dataset=tokenized_dataset,
data_collator=DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm=False), # 不是掩码语言模型
)
print("开始训练你的‘小红书博主’模型...")
trainer.train()
# 保存我们训练好的“技能马甲”
trainer.model.save_pretrained("./my_xiaohongshu_lora_adapter")
from peft import PeftModel
# 加载基础模型
base_model = AutoModelForCausalLM.from_pretrained(
local_model_path,
trust_remote_code=True,
torch_dtype=torch.float32,
device_map=device
)
# 加载我们刚训练好的LoRA适配器,把它“穿”在基础模型上
model = PeftModel.from_pretrained(base_model, "./my_xiaohongshu_lora_adapter")
model.eval()
# 现在,让它生成文案吧!
prompt = "### 指令:\n写一篇关于‘秋天的第一杯奶茶’的小红书文案\n\n### 回答:\n"
inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
outputs = model.generate(
**inputs,
max_new_tokens=200, # 最多生成200个新token
temperature=0.8, # 控制创造性,越低越确定,越高越随机
do_sample=True,
top_p=0.9, # 核采样,让生成质量更高
)
generated_text = tokenizer.decode(outputs[0], skip_special_tokens=True)
print("【微调后】", generated_text)正在下载/校验模型缓存... Downloading Model from https://www.modelscope.cn to directory: D:\modelscope\hub\qwen\Qwen1.5-0.5B 2025-10-27 21:39:27,765 - modelscope - INFO - Creating symbolic link [D:\modelscope\hub\qwen\Qwen1.5-0.5B]. 2025-10-27 21:39:27,765 - modelscope - WARNING - Failed to create symbolic link D:\modelscope\hub\qwen\Qwen1.5-0.5B for D:\modelscope\hub\qwen\Qwen1___5-0___5B. 正在加载模型... 模型加载完成! 开始生成微调前基线输出... 【微调前】 ### 指令: 写一篇关于‘秋天的第一杯奶茶’的小红书文案 ### 回答: 这个小红书文案可以描述秋天奶茶的特点,包括奶茶的口感、奶茶的香气和奶茶的配料等等。此外,还可以描述秋天奶茶的配色和氛围,例如深色调的奶茶和秋季的自然风景,或者一些特别的饮品或小吃,如秋季的果味蛋糕、秋日的熏烤等。总之,这个小红书文案可以是关于秋天奶茶的详细描述,也可以是对于秋天奶茶的推荐和建议。 trainable params: 7,569,408 || all params: 471,557,120 || trainable%: 1.6052 Map: 100%|████████████████████████| 2/2 [00:00<00:00, 199.23 examples/s] Map: 100%|████████████████████████| 2/2 [00:00<00:00, 180.77 examples/s] 开始训练你的‘小红书博主’模型... {'train_runtime': 7.0945, 'train_samples_per_second': 0.846, 'train_steps_per_second': 0.423, 'train_loss': 4.294288317362468, 'epoch': 3.0} 100%|██████████████████████████████████| 3/3 [00:07<00:00, 2.36s/it] Setting `pad_token_id` to `eos_token_id`:None for open-end generation. 【微调后】 ### 指令: 写一篇关于‘秋天的第一杯奶茶’的小红书文案 ### 回答: 这是一个关于秋天的第一杯奶茶的文案。 ### 我们每个人都可以制作奶茶,因为秋天的第一杯奶茶,是秋天最美好的回忆。 ### 那么,你是否也想尝尝秋天的第一杯奶茶呢?那就快来试试吧! ### 你可以在Instagram上关注@小红书奶茶,获取更多关于秋天奶茶的食谱和制作方法。 ### 除了奶茶,秋天还有许多美好的事物可以享用,例如烤秋千,烤红薯,烤苹果等等。快来试试吧! ### 小红书奶茶的制作方法:1、将红茶和牛奶倒入奶锅中,用小火煮开。2、将煮好的牛奶倒入搅拌器中,用电动搅拌器搅拌至奶泡出现。3、 将牛奶倒入杯中,加入蜂蜜或糖,用搅拌器搅拌至浓稠。4、将奶茶倒入杯中,放入冰箱冷藏2小时以上。5、取出杯中,放入冰箱冷藏
trainable params: 8,388,608 || all params: 7,737,909,248 || trainable%: 0.1084
这个输出说明这个过程只训练了总参数的 0.1%!

训练完成后,你会在 ./my_xiaohongshu_lora_adapter 文件夹里找到几个文件,这就是LoRA适配器,可能只有几十MB。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。