当前位置: 首页 > news >正文

别再死记硬背了!用PyTorch代码逐行拆解Transformer中的QKV矩阵计算

用PyTorch代码逐行拆解Transformer中的QKV矩阵计算

在自然语言处理领域,Transformer架构已经成为事实上的标准。但很多开发者发现,仅通过理论图示理解其核心的注意力机制仍然存在困难。本文将带你用PyTorch代码从零开始实现QKV矩阵的计算过程,通过实际运行和调试来直观感受信息流动。

1. 准备工作与环境搭建

首先确保你的开发环境已经安装了最新版本的PyTorch。如果你使用Colab,可以直接运行以下代码安装:

!pip install torch torchvision

接下来导入必要的库:

import torch import torch.nn as nn import torch.nn.functional as F import numpy as np import matplotlib.pyplot as plt

为了更清晰地观察矩阵变化,我们定义一个辅助函数来打印张量信息:

def print_tensor_info(name, tensor): print(f"{name}: shape={tensor.shape}, dtype={tensor.dtype}") print(tensor)

2. 基础QKV计算实现

让我们从最基本的单头注意力开始,理解QKV矩阵的生成过程。

2.1 定义线性变换层

在Transformer中,QKV矩阵是通过对输入进行线性变换得到的:

class SelfAttention(nn.Module): def __init__(self, embed_size, heads): super(SelfAttention, self).__init__() self.embed_size = embed_size self.heads = heads self.head_dim = embed_size // heads assert ( self.head_dim * heads == embed_size ), "Embedding size needs to be divisible by heads" self.values = nn.Linear(embed_size, embed_size) self.keys = nn.Linear(embed_size, embed_size) self.queries = nn.Linear(embed_size, embed_size) self.fc_out = nn.Linear(embed_size, embed_size)

2.2 生成QKV矩阵

现在我们实现前向传播过程,观察QKV矩阵的实际计算:

def forward(self, values, keys, query, mask): N = query.shape[0] # 批大小 value_len, key_len, query_len = values.shape[1], keys.shape[1], query.shape[1] # 线性变换得到QKV values = self.values(values) # (N, value_len, embed_size) keys = self.keys(keys) # (N, key_len, embed_size) queries = self.queries(query) # (N, query_len, embed_size) # 打印变换后的矩阵形状 print_tensor_info("Values after linear", values) print_tensor_info("Keys after linear", keys) print_tensor_info("Queries after linear", queries) # 分割多头 values = values.reshape(N, value_len, self.heads, self.head_dim) keys = keys.reshape(N, key_len, self.heads, self.head_dim) queries = queries.reshape(N, query_len, self.heads, self.head_dim) # 更多调试信息...

3. 三种注意力机制的QKV实现差异

Transformer中有三种不同的注意力机制,它们的QKV来源各不相同。让我们分别实现并观察差异。

3.1 编码器自注意力

在编码器自注意力中,QKV都来自同一个输入:

# 模拟编码器输入 batch_size = 2 seq_length = 5 embed_size = 512 dummy_input = torch.randn(batch_size, seq_length, embed_size) # 初始化注意力层 encoder_attention = SelfAttention(embed_size, heads=8) # 自注意力:QKV都来自同一输入 Q = encoder_attention.queries(dummy_input) K = encoder_attention.keys(dummy_input) V = encoder_attention.values(dummy_input) print("Encoder Self-Attention:") print_tensor_info("Q", Q) print_tensor_info("K", K) print_tensor_info("V", V)

3.2 解码器自注意力

解码器自注意力需要添加掩码,防止看到未来信息:

# 模拟解码器输入 decoder_input = torch.randn(batch_size, seq_length, embed_size) # 生成掩码 mask = torch.tril(torch.ones(seq_length, seq_length)).expand( batch_size, 1, seq_length, seq_length ) decoder_attention = SelfAttention(embed_size, heads=8) Q = decoder_attention.queries(decoder_input) K = decoder_attention.keys(decoder_input) V = decoder_attention.values(decoder_input) print("\nDecoder Masked Self-Attention:") print_tensor_info("Mask", mask) print_tensor_info("Q", Q) print_tensor_info("K", K) print_tensor_info("V", V)

3.3 编码器-解码器注意力

这是跨注意力机制,Q来自解码器,KV来自编码器:

# 模拟编码器输出 encoder_output = torch.randn(batch_size, seq_length, embed_size) cross_attention = SelfAttention(embed_size, heads=8) Q = cross_attention.queries(decoder_input) # Q来自解码器 K = cross_attention.keys(encoder_output) # K来自编码器 V = cross_attention.values(encoder_output) # V来自编码器 print("\nEncoder-Decoder Attention:") print_tensor_info("Q (from decoder)", Q) print_tensor_info("K (from encoder)", K) print_tensor_info("V (from encoder)", V)

4. 注意力计算与可视化

理解了QKV的来源后,让我们实现完整的注意力计算过程。

4.1 计算注意力分数

def scaled_dot_product_attention(Q, K, V, mask=None): d_k = Q.size(-1) attention_scores = torch.matmul(Q, K.transpose(-2, -1)) / torch.sqrt(torch.tensor(d_k)) if mask is not None: attention_scores = attention_scores.masked_fill(mask == 0, float("-1e20")) attention_weights = F.softmax(attention_scores, dim=-1) output = torch.matmul(attention_weights, V) return output, attention_weights

4.2 可视化注意力权重

让我们可视化三种不同注意力机制的权重分布:

def plot_attention(attention_weights, title): plt.figure(figsize=(10, 5)) plt.imshow(attention_weights[0, 0].detach().numpy(), cmap='viridis') plt.colorbar() plt.title(title) plt.xlabel("Key Positions") plt.ylabel("Query Positions") plt.show() # 编码器自注意力 encoder_output, encoder_weights = scaled_dot_product_attention(Q, K, V) plot_attention(encoder_weights, "Encoder Self-Attention Weights") # 解码器自注意力(带掩码) decoder_output, decoder_weights = scaled_dot_product_attention(Q, K, V, mask) plot_attention(decoder_weights, "Decoder Masked Self-Attention Weights") # 编码器-解码器注意力 cross_output, cross_weights = scaled_dot_product_attention(Q, K, V) plot_attention(cross_weights, "Encoder-Decoder Attention Weights")

5. 多头注意力实现

最后,我们实现完整的多头注意力机制,观察QKV在多个头中的不同表现。

5.1 多头注意力前向传播

def forward(self, values, keys, query, mask): N = query.shape[0] value_len, key_len, query_len = values.shape[1], keys.shape[1], query.shape[1] values = self.values(values) # (N, value_len, embed_size) keys = self.keys(keys) # (N, key_len, embed_size) queries = self.queries(query) # (N, query_len, embed_size) # 分割多头 values = values.reshape(N, value_len, self.heads, self.head_dim) keys = keys.reshape(N, key_len, self.heads, self.head_dim) queries = queries.reshape(N, query_len, self.heads, self.head_dim) # 计算注意力 energy = torch.einsum("nqhd,nkhd->nhqk", [queries, keys]) if mask is not None: energy = energy.masked_fill(mask == 0, float("-1e20")) attention = torch.softmax(energy / (self.embed_size ** (1/2)), dim=3) out = torch.einsum("nhql,nlhd->nqhd", [attention, values]).reshape( N, query_len, self.heads * self.head_dim ) out = self.fc_out(out) return out

5.2 观察不同头的注意力模式

# 初始化多头注意力 multihead_attn = SelfAttention(embed_size=512, heads=8) # 编码器自注意力 output = multihead_attn(dummy_input, dummy_input, dummy_input, None) # 提取第一个样本的第一个token在各头的注意力权重 sample_weights = attention[0, :, 0, :] # (heads, key_len) # 绘制各头的注意力模式 plt.figure(figsize=(12, 6)) for i in range(8): plt.subplot(2, 4, i+1) plt.plot(sample_weights[i].detach().numpy()) plt.title(f"Head {i+1}") plt.tight_layout() plt.show()

通过实际运行这些代码,你可以清晰地看到QKV矩阵在不同注意力机制中的生成过程和数据流动。这种动手实践的方式比单纯看理论图示更能加深对Transformer核心机制的理解。

http://www.cnnetsun.cn/news/1662927.html

相关文章:

  • Riffusion 音频生成 API 集成指南
  • Hunyuan-MT-7B GPU部署:Pixel Language Portal在单卡A10上并发处理16路实时语音翻译压测报告
  • OpenClaw技能开发入门:为千问3.5-35B-A3B-FP8编写图片处理插件
  • 3D医学影像分割实战:从数据预处理到模型训练全流程解析
  • 告别黑箱预测:用TFT模型搞定电力负荷与销量预测,还能看懂模型在想什么
  • Wan2.2-I2V-A14B长视频拼接:多段10秒视频无缝衔接生成60秒方案
  • 开源大模型部署教程:Pixel Epic智识终端+AgentCPM-Report零基础搭建
  • Pixel Aurora Engine 提示词安全与内容过滤:构建负责任的AI应用
  • 【仅开放72小时】C++27实验性parallel_unstable_sort_view深度评测:多核排序吞吐达1.2GB/s的编译器flag调优矩阵(附Intel Xeon W9-3400实测数据)
  • SEO_快速见效的SEO实操技巧与工具推荐
  • intv_ai_mk11效果实测:技术面试题生成能力——覆盖算法/系统设计/行为问题
  • 百川2-13B-4bits量化版+OpenClaw:智能家居控制中心改造
  • AI开发效率翻倍:TensorFlow-v2.9镜像完整开发环境实测体验
  • Qwen3智能字幕对齐系统Mathtype公式识别挑战与解决方案
  • Phi-4-mini-reasoning助力VSCode开发:智能代码补全与问题诊断实战
  • 【bilibili-downloader】:突破4K画质限制的B站视频下载工具:给视频收藏爱好者的高效解决方案
  • 设计行业AI转型:从创意出图到落地交付的全流程效率提升
  • OpenClaw浏览器自动化:配合Phi-3-vision-128k-instruct实现网页图文抓取
  • 告别云端依赖!DeepSeek-R1-Distill-Qwen-1.5B离线运行全攻略
  • 该SSD固态硬盘告诉你:航天级芯片如何解决企业数据存储的卡顿与安全痛点?
  • SEO_ 2024年必须知道的7个核心SEO技巧与策略
  • 零代码美化Neeshck-Z-lmage_LYX_v2界面:Streamlit主题定制完整教程
  • RTEdbg —— 嵌入式实时调试的“瑞士军刀“
  • 复盘文化:不让任何一个线上事故白白发生
  • 2026最新降AI率工具测评:嘎嘎降AI、比话降AI、率零实测对比
  • 网络seo优化公司与其他营销方式的区别是什么
  • COMSOL二维六边形光子晶体能带分析:三角晶格TE与TM模式的区分与结果,以及Y轴晶格周期a...
  • OpenClaw社区贡献指南:为Qwen3-14b_int4_awq开发并分享自定义技能
  • Nunchaku-flux-1-dev自动化运维:编写脚本实现模型服务监控与重启
  • OpenClaw安装部署Windows操作系统版 - 手把手教你搭建AI智能体平台