【深度学习】Transformer 注意力机制与 LoRA target_modules 详解
1. Transformer 自注意力机制结构
1.1 基础组件
class SelfAttention(nn.Module):def __init__(self, d_model, n_heads):super().__init__()self.d_model = d_modelself.n_heads = n_headsself.d_k = d_model // n_heads# 这就是 target_modules 中的四个关键组件!self.q_proj = nn.Linear(d_model, d_model) # Query 投影self.k_proj = nn.Linear(d_model, d_model) # Key 投影 self.v_proj = nn.Linear(d_model, d_model) # Value 投影self.o_proj = nn.Linear(d_model, d_model) # Output 投影def forward(self, x):batch_size, seq_len, d_model = x.shape# 1. 通过线性变换得到 Q, K, VQ = self.q_proj(x) # [batch, seq_len, d_model]K = self.k_proj(x) # [batch, seq_len, d_model] V = self.v_proj(x) # [batch, seq_len, d_model]# 2. 重塑为多头形式Q = Q.view(batch_size, seq_len, self.n_heads, self.d_k).transpose(1, 2)K = K.view(batch_size, seq_len, self.n_heads, self.d_k).transpose(1, 2)V = V.view(batch_size, seq_len, self.n_heads, self.d_k).transpose(1, 2)# 3. 计算注意力attention_scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k)attention_weights = F.softmax(attention_scores, dim=-1)attention_output = torch.matmul(attention_weights, V)# 4. 合并多头输出attention_output = attention_output.transpose(1, 2).contiguous().view(batch_size, seq_len, d_model)# 5. 通过输出投影output = self.o_proj(attention_output) # 最后的线性变换return output
2. 每个投影矩阵的作用详解
2.1 q_proj (Query Projection)
# 作用:将输入转换为"查询"向量
# 可以理解为:我在寻找什么信息?def explain_q_proj():# 假设输入是:"我喜欢吃苹果"input_embedding = torch.randn(1, 5, 768) # [batch, seq_len, hidden_size]q_proj = nn.Linear(768, 768)queries = q_proj(input_embedding)print("Query的语义解释:")print("- 对于'我'这个token:它可能查询'谁在执行动作'")print("- 对于'喜欢':它可能查询'情感状态相关的词'") print("- 对于'苹果':它可能查询'与食物相关的动作'")return queries
2.2 k_proj (Key Projection)
# 作用:将输入转换为"键"向量
# 可以理解为:我能提供什么信息?def explain_k_proj():input_embedding = torch.randn(1, 5, 768)k_proj = nn.Linear(768, 768) keys = k_proj(input_embedding)print("Key的语义解释:")print("- '我'的key:'我是一个执行者,主语'")print("- '喜欢'的key:'我是一个表达情感的动词'")print("- '苹果'的key:'我是一个食物名词,可以被喜欢'")return keys
2.3 v_proj (Value Projection)
# 作用:将输入转换为"值"向量
# 可以理解为:我实际包含的信息内容是什么?def explain_v_proj():input_embedding = torch.randn(1, 5, 768)v_proj = nn.Linear(768, 768)values = v_proj(input_embedding)print("Value的语义解释:")print("- '我'的value:包含主语、人称等语义信息")print("- '喜欢'的value:包含正面情感、偏好等语义信息") print("- '苹果'的value:包含水果、食物、健康等语义信息")return values
2.4 o_proj (Output Projection)
# 作用:将多头注意力的结果投影回原始维度
# 可以理解为:整合所有注意力信息并输出def explain_o_proj():# 假设多头注意力已经计算完毕multi_head_output = torch.randn(1, 5, 768) # 已经concat了所有heado_proj = nn.Linear(768, 768)final_output = o_proj(multi_head_output)print("Output Projection的作用:")print("- 将8个注意力头的信息融合")print("- 学习如何最好地组合不同类型的注意力")print("- 为下一层提供refined的表示")return final_output
3. 为什么选择这四个模块进行 LoRA?
3.1 参数量分析
def analyze_parameter_distribution():# 假设 d_model = 768 的模型d_model = 768modules = {'q_proj': d_model * d_model, # 768 * 768 = 589,824'k_proj': d_model * d_model, # 768 * 768 = 589,824 'v_proj': d_model * d_model, # 768 * 768 = 589,824'o_proj': d_model * d_model, # 768 * 768 = 589,824'feed_forward': d_model * d_model * 4, # 通常是4倍大小}total_attention_params = sum([modules[k] for k in ['q_proj', 'k_proj', 'v_proj', 'o_proj']])total_ff_params = modules['feed_forward']print(f"注意力层参数: {total_attention_params:,}")print(f"前馈层参数: {total_ff_params:,}")print(f"注意力层占比: {total_attention_params/(total_attention_params+total_ff_params)*100:.1f}%")return modules# 输出:
# 注意力层参数: 2,359,296
# 前馈层参数: 2,359,296
# 注意力层占比: 50.0%
3.2 为什么注意力层是最佳选择?
语义理解核心
注意力机制是 Transformer 理解语义关系的核心:
- Q-K 交互决定了词与词之间的关联强度
- V 决定了传递什么信息
- O 决定了如何整合这些信息
影响范围广
每个 attention head 都学习不同类型的语义关系:
- Head 1: 可能学习语法依赖关系
- Head 2: 可能学习语义相似性
- Head 3: 可能学习长距离依赖
参数效率高
相比全量微调:
- 原始参数: 589,824 * 4 = 2,359,296 per layer
- LoRA参数 (r=8): (7688 + 8768) * 4 = 49,152 per layer
- 效率提升: 98% 参数减少!
4. target_modules 的不同选择策略
4.1 完整注意力策略(推荐)
最常用的配置
target_modules = ["q_proj", "k_proj", "v_proj", "o_proj"]
优点:
- 覆盖注意力机制的所有关键组件
- 平衡性能和效率
- 适用于大多数微调任务
4.2 精简策略
只微调 Q 和 V
target_modules = ["q_proj", "v_proj"]
适用场景:
- 计算资源极度受限
- 任务相对简单
- 快速原型验证
理由:Q 决定查询什么,V 决定传递什么信息,这两个最核心
4.3 扩展策略
包含前馈网络
target_modules = ["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"]
适用场景:
- 需要深度适应特定领域
- 计算资源充足
- 复杂的微调任务
注意:参数量会显著增加
4.4 实验对比
def compare_strategies():strategies = {"minimal": ["q_proj", "v_proj"],"standard": ["q_proj", "k_proj", "v_proj", "o_proj"], "extended": ["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj"]}for name, modules in strategies.items():param_count = len(modules) * (768 * 8 + 8 * 768) # 假设 r=8print(f"{name:10}: {len(modules)} 模块, ~{param_count:,} 参数")# 输出:
# minimal : 2 模块, ~24,576 参数
# standard : 4 模块, ~49,152 参数
# extended : 6 模块, ~73,728 参数
5. 实际代码示例
5.1 查看模型结构
from transformers import AutoModeldef inspect_model_modules():model = AutoModel.from_pretrained("Qwen/Qwen3-Embedding-0.6B")print("模型的主要模块:")for name, module in model.named_modules():if any(target in name for target in ["q_proj", "k_proj", "v_proj", "o_proj"]):print(f" {name}: {module}")# 输出类似:
# model.encoder.layer.0.attention.self.q_proj: Linear(768, 768)
# model.encoder.layer.0.attention.self.k_proj: Linear(768, 768)
# model.encoder.layer.0.attention.self.v_proj: Linear(768, 768)
# model.encoder.layer.0.attention.output.o_proj: Linear(768, 768)
5.2 LoRA 配置与应用
from peft import LoraConfig, get_peft_modeldef apply_lora_with_explanation():base_model = AutoModel.from_pretrained("Qwen/Qwen3-Embedding-0.6B")# 查看原始参数量original_params = sum(p.numel() for p in base_model.parameters())print(f"原始模型参数: {original_params:,}")# 应用 LoRAlora_config = LoraConfig(r=8, # 低秩分解的秩lora_alpha=16, # 缩放因子target_modules=["q_proj", "k_proj", "v_proj", "o_proj"], # 目标模块lora_dropout=0.05,bias="none",task_type=TaskType.FEATURE_EXTRACTION)model = get_peft_model(base_model, lora_config)# 查看可训练参数trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad)total_params = sum(p.numel() for p in model.parameters())print(f"总参数: {total_params:,}")print(f"可训练参数: {trainable_params:,}") print(f"可训练比例: {trainable_params/total_params*100:.2f}%")# 显示具体的 LoRA 模块model.print_trainable_parameters()return model
5.3 不同 target_modules 的效果对比
def compare_target_modules():base_model = AutoModel.from_pretrained("Qwen/Qwen3-Embedding-0.6B")configurations = {"只有QV": ["q_proj", "v_proj"],"标准QKV": ["q_proj", "k_proj", "v_proj"], "完整注意力": ["q_proj", "k_proj", "v_proj", "o_proj"],"包含FFN": ["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj"]}print("不同配置的参数对比:")print("-" * 50)for config_name, target_modules in configurations.items():lora_config = LoraConfig(r=8,lora_alpha=16,target_modules=target_modules,lora_dropout=0.05,bias="none",task_type=TaskType.FEATURE_EXTRACTION)try:model = get_peft_model(base_model, lora_config)trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad)print(f"{config_name:12}: {trainable_params:,} 个可训练参数")except Exception as e:print(f"{config_name:12}: 配置错误 - {e}")
6. 选择建议
6.1 基于任务类型选择
task_recommendations = {"句子相似性": ["q_proj", "k_proj", "v_proj", "o_proj"],"文档分类": ["q_proj", "v_proj"], # 简化版本通常足够"语义搜索": ["q_proj", "k_proj", "v_proj", "o_proj"], # 需要完整注意力"多语言适配": ["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj"], # 扩展版本"领域适应": ["q_proj", "k_proj", "v_proj", "o_proj"] # 标准配置
}for task, modules in task_recommendations.items():print(f"{task}: {modules}")
6.2 基于资源限制选择
def resource_based_selection(gpu_memory_gb, training_time_hours):if gpu_memory_gb < 8:return ["q_proj", "v_proj"]elif gpu_memory_gb < 16:return ["q_proj", "k_proj", "v_proj"] else:return ["q_proj", "k_proj", "v_proj", "o_proj"]# 示例
print("8GB GPU:", resource_based_selection(8, 2)) # ['q_proj', 'v_proj']
print("16GB GPU:", resource_based_selection(16, 4)) # ['q_proj', 'k_proj', 'v_proj']
print("24GB GPU:", resource_based_selection(24, 8)) # ['q_proj', 'k_proj', 'v_proj', 'o_proj']
总结
target_modules=["q_proj", "k_proj", "v_proj", "o_proj"]
的选择是基于以下考虑:
- 覆盖注意力核心:这四个模块构成了自注意力机制的完整流程
- 参数效率平衡:在性能和计算效率之间取得最佳平衡
- 广泛适用性:适用于大多数 embedding 微调任务
- 实践验证:在众多实验中证明了其有效性
对于 embedding 模型微调,这个配置通常是最佳选择!