目 录CONTENT

文章目录

【深度学习】Transformer 注意力机制与 LoRA target_modules 详解

EulerBlind
2025-08-26 / 0 评论 / 6 点赞 / 411 阅读 / 0 字

1. Transformer 自注意力机制结构

1.1 基础组件

class SelfAttention(nn.Module):
    def __init__(self, d_model, n_heads):
        super().__init__()
        self.d_model = d_model
        self.n_heads = n_heads
        self.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, V
        Q = 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了所有head
    
    o_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 = 768
    
    modules = {
        '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=8
        print(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 AutoModel

def 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_model

def 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:,}")
    
    # 应用 LoRA
    lora_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"] 的选择是基于以下考虑:

  1. 覆盖注意力核心:这四个模块构成了自注意力机制的完整流程
  2. 参数效率平衡:在性能和计算效率之间取得最佳平衡
  3. 广泛适用性:适用于大多数 embedding 微调任务
  4. 实践验证:在众多实验中证明了其有效性

对于 embedding 模型微调,这个配置通常是最佳选择!

6

评论区