LLM:位置编码详解与实现

文章目录

  • 前言
  • 一、绝对位置编码
  • 二、相对位置编码
  • 三、旋转位置编码


前言

由于attetnion运算的特性,Transformer本身不感知顺序,位置编码是弥补这一缺陷的关键。


一、绝对位置编码

绝对位置编码的方式是通过将每个位置映射到一个高维空间中,该编码采用了正弦和余弦函数的组合请添加图片描述
周期性:正弦和余弦函数具有周期性,这使得编码能够容易地表示固定范围的位置信息。这个特点允许模型有能力处理序列的循环性质,例如在语言中,某些词可能在不同上下文中重复出现,但它们的相对位置仍然是重要的。

不同频率:对于不同的维度 ( i ),使用不同的频率来编码位置。低维度的编码(例如低 ( i ) 值)对应较大的周期,能够捕捉较长的依赖关系;而高维度编码对应较小的周期,能够捕捉短距离的依赖关系。因此,模型可以通过不同的维度考虑不同尺度的位置信息

任意长度的序列:这种方法能够处理任意长度的输入序列,因为正弦和余弦函数是定义于所有实数的,可以为任意的位置提供唯一的编码。

尽管绝对位置编码看似只提供了位置的信息,但模型在训练过程中会学会捕捉相对位置信息。原因如下:1、位置之间的差异:通过将位置编码加到输入向量中,模型可以学习到位置之间的相对关系。例如,给定位置 ( i ) 和位置 ( j ),它们的编码向量可以表示为:
PE(i)−PE(j)
这一差值可以在一定程度上反映这两个位置之间的相对距离。
2、向量性质:在高维空间中,向量之间的方向和距离能够也反映相对位置。例如,当两个词在序列中相隔一定距离时,它们的相应位置编码的差异会隐含这种相对关系。
import torch
import torch.nn as nn
import mathclass PositionalEncoding(nn.Module):"""实现经典的基于正弦和余弦函数的绝对位置编码。"""def __init__(self, d_model, max_len=5000, dropout=0.1):"""Args:d_model (int): 模型的维度(或词嵌入的维度)。max_len (int): 预先计算编码的最大序列长度。dropout (float): Dropout 的比例。"""super(PositionalEncoding, self).__init__()self.dropout = nn.Dropout(p=dropout)# 创建一个足够长的位置编码矩阵# 形状为 (max_len, d_model)pe = torch.zeros(max_len, d_model)# 创建一个位置索引张量# position.shape: (max_len, 1)position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)# 计算除法项 1 / (10000^(2i/d_model))# div_term.shape: (d_model/2)# 这里的 log 是为了数值稳定性,等价于 1 / (10000^(2i/d_model))div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model))# 使用广播机制计算正弦和余弦值# 偶数索引使用 sinpe[:, 0::2] = torch.sin(position * div_term)# 奇数索引使用 cospe[:, 1::2] = torch.cos(position * div_term)# 增加一个 batch 维度,使其能够与输入张量 (batch_size, seq_len, d_model) 直接相加# pe.shape from (max_len, d_model) to (1, max_len, d_model)pe = pe.unsqueeze(0)# 将 pe 注册为 buffer。# buffer 是模型的状态的一部分,但不是参数 (parameters),因此不会被优化器更新。# 它会随着模型一起移动(例如 .to(device)),并且会保存在 state_dict 中。self.register_buffer('pe', pe)def forward(self, x):"""将位置编码添加到输入张量中。Args:x (torch.Tensor): 输入张量,形状为 (batch_size, seq_len, d_model)。Returns:torch.Tensor: 添加了位置编码的输出张量,形状与输入相同。"""# x.shape: (batch_size, seq_len, d_model)# self.pe.shape: (1, max_len, d_model)# 截取所需长度的位置编码并与输入相加# self.pe[:, :x.size(1), :] 的形状变为 (1, seq_len, d_model),可以与 x 广播相加x = x + self.pe[:, :x.size(1), :]return self.dropout(x)

二、相对位置编码

相对位置编码(Relative Positional Encoding)是一种改善模型捕捉序列中词语相对位置关系的技术。与绝对位置编码(Absolute Positional Encoding)不同,相对位置编码侧重于表示词与词之间的相对距离,从而增强模型的学习能力。

但相对位置编码的实现和绝对位置编码的实现差距还是挺大的,相对位置编码以矩阵的形式呈现,计算每个词i 和 词j 的相对位置,如果序列长度过大,会导致整个位置编码十分庞大,计算成本巨大。

标准的相对位置编码方法(如论文 “Self-Attention with Relative Position Representations” 中提出的)并不会直接预计算所有位置对的编码。相反,它创建了一个可学习的相对位置嵌入——relative position embedding查找表。

实现:

import torch
import torch.nn as nn
import mathclass RelativeAttentionScore(nn.Module):def __init__(self, d_model, max_len=50):super(RelativeAttentionScore, self).__init__()# 假设 d_k = d_model,在多头注意力中 d_k = d_model / num_headsself.d_k = d_model# 定义一个最大相对距离。超过这个距离的位置被视为相同距离。# 这有助于模型泛化到比训练时更长的序列。self.max_relative_position = max_len# 创建一个可学习的嵌入查找表,用于相对位置。# 大小为 2 * max_len - 1,覆盖从 -(max_len-1) 到 (max_len-1) 的所有相对位置。# +1 是因为我们需要一个位置来存储被裁剪的距离self.relative_embeddings = nn.Embedding(2 * self.max_relative_position + 1, self.d_k)def forward(self, queries, keys):"""Args:queries (torch.Tensor): 查询张量,形状为 (batch_size, seq_len, d_model)keys (torch.Tensor): 键张量,形状为 (batch_size, seq_len, d_model)Returns:torch.Tensor: 加上了相对位置偏置的注意力得分,形状为 (batch_size, seq_len, seq_len)"""batch_size, seq_len, _ = queries.shape# 1. 计算基于内容的注意力得分content_score = torch.matmul(queries, keys.transpose(-2, -1))# 2. 计算基于位置的注意力得分# a. 生成相对位置矩阵# range_vec.shape: (seq_len)range_vec = torch.arange(seq_len, device=queries.device)# relative_matrix.shape: (seq_len, seq_len)# 每一行表示当前位置与所有其他位置的相对距离relative_matrix = range_vec[None, :] - range_vec[:, None]print(relative_matrix)# b. 裁剪相对距离并移动到非负索引# 将距离裁剪到 [-max_relative_position, max_relative_position] 范围内#    print(self.max_relative_position)clipped_relative_matrix = torch.clamp(relative_matrix, -self.max_relative_position, self.max_relative_position)# 将索引平移到 [0, 2 * max_relative_position] 范围,以用于Embedding查找positive_indices = clipped_relative_matrix + self.max_relative_position# c. 查找相对位置嵌入# pos_embeddings.shape: (seq_len, seq_len, d_k)pos_embeddings = self.relative_embeddings(positive_indices)# d. 计算位置得分# 我们需要计算 query 和 相对位置嵌入 的点积。# (b, i, d) * (i, j, d) -> (b, i, j)# b=batch_size, i=query_pos, j=key_pos, d=d_k# torch.einsum 是实现这种复杂矩阵乘法的优雅方式position_score = torch.einsum('bid,ijd->bij', queries, pos_embeddings)# 3. 将内容得分和位置得分相加attention_scores = content_score + position_score# (可选)应用缩放scaled_attention_scores = attention_scores / math.sqrt(self.d_k)return scaled_attention_scores
--- 相对位置矩阵示例 (seq_len=5) ---
原始相对位置矩阵:tensor([[ 0,  1,  2,  3,  4],[-1,  0,  1,  2,  3],[-2, -1,  0,  1,  2],[-3, -2, -1,  0,  1],[-4, -3, -2, -1,  0]])裁剪后的矩阵 (max_len=2):tensor([[ 0,  1,  2,  2,  2],[-1,  0,  1,  2,  2],[-2, -1,  0,  1,  2],[-2, -2, -1,  0,  1],[-2, -2, -2, -1,  0]])用于嵌入查找的非负索引:tensor([[2, 3, 4, 4, 4],[1, 2, 3, 4, 4],[0, 1, 2, 3, 4],[0, 0, 1, 2, 3],[0, 0, 0, 1, 2]])

由于裁剪机制的存在,任何超出 [-max_relative_position, max_relative_position] 范围的相对距离都会被“压缩”到边界值上。这个机制其实很好的帮助了模型:

1)假设用最大长度为512的句子训练了一个模型,max_relative_position 也设为512。现在,想用这个模型去处理一个长度为1024的句子。如果没有裁剪:模型在处理这个长句子时,会遇到它从未见过的相对距离,比如 +600 或 -800。由于嵌入表中没有这些距离的位置,或者这些位置的嵌入向量从未被训练过,模型的表现会变得非常不稳定,甚至完全崩溃

有了裁剪:模型在训练时已经学会了一个对于“非常远”的距离(比如+512或-512)的通用表示。当它在推理时遇到一个新的、更远的距离(如 +600)时,它会将其裁剪到 +512,然后使用那个它已经熟知的“非常远”的嵌入。这使得模型能够平滑地泛化到比训练时更长的序列,而不会因为遇到未知的距离而失败。

2)在自然语言中,词语之间的关系强度通常与距离有关,但这种关系不是无限延伸的。模型通过裁剪,学会了一个“局部注意力窗口”(在 [-50, 50] 范围内),并对这个窗口内的位置进行精细建模。对于窗口外的所有位置,它只学习一个统一的“远距离”表示。这是一种非常合理的归纳偏置(inductive bias)。

三、旋转位置编码

好的,旋转位置编码(Rotary Positional Encoding, RoPE)是目前大型语言模型(如 LLaMA, PaLM)中非常流行且效果出色的一种位置编码方案。它由苏建林在论文《RoFormer: Enhanced Transformer with Rotary Position Embedding》中提出。

与传统的加性位置编码(Absolute PE)或在注意力分数上增加偏置(Relative PE from Shaw et al.)不同,RoPE 的思想极为巧妙:它通过旋转查询(Query)和键(Key)向量来注入位置信息。

其核心思想为:
绝对位置决定初始角度:一个词在序列中的绝对位置 m 决定了它的查询向量 q 和键向量 k 需要旋转的角度 mθ。相对位置体现在角度差:当计算两个词(位置m的查询q和位置n的键k)的注意力时,它们旋转后的点积结果,神奇地只与它们的内容 (q, k) 和它们的相对位置 m-n 有关,而与它们的绝对位置 m 和 n 无关

数学原理为:
RoPE 的魔法在于复数运算。将 d 维的向量两两配对,看作 d/2 个复数。对一个位于位置 m 的向量 x(它可以是 q 或 k),其旋转操作可以表示为:
x’_m = x_m * e^(i * m * θ) ; x_m 是原始向量(被看作复数)。 i 是虚数单位。 m 是绝对位置。θ 是一个预设的、与维度相关的常数(类似于传统PE中的频率)

当计算旋转后的查询 q’_m 和键 k’_n 的点积(在复数域中是取共轭后相乘再取实部)时:
Re[ (q_m * e^(imθ)) * (k_n * e(i*nθ)) ]
= Re[ q_m * k_n^* * e^(imθ) * e^(-inθ) ]
= Re[ q_m * k_n^* * e^(i*(m-n)θ) ]
最终结果仅依赖于m-n

import torch
import torch.nn as nn
import mathclass RotaryPositionalEncoding(nn.Module):def __init__(self, d_model, max_len=512):super().__init__()self.d_model = d_model# 计算旋转角度 theta# theta_i = 10000^(-2(i-1)/d) for i in [1, 2, ..., d/2]inv_freq = 1.0 / (10000 ** (torch.arange(0, d_model, 2).float() / d_model))# 预先计算所有可能位置 m 的 m*thetat = torch.arange(max_len, dtype=inv_freq.dtype)freqs = torch.einsum('i,j->ij', t, inv_freq)# freqs 包含了所有位置的 m*theta, 形状是 (max_len, d_model/2)# 将其扩展为 (max_len, d_model) 以便应用# emb.shape: (max_len, d_model)emb = torch.cat((freqs, freqs), dim=-1)# 注册为 buffer,这样它就不会被视为模型参数,但会随模型移动 (e.g., .to(device))# self.cos_cached.shape: (1, 1, max_len, d_model)# self.sin_cached.shape: (1, 1, max_len, d_model)self.register_buffer("cos_cached", emb.cos()[None, None, :, :])self.register_buffer("sin_cached", emb.sin()[None, None, :, :])def forward(self, x):# x.shape: (batch_size, num_heads, seq_len, head_dim)# head_dim == self.d_modelseq_len = x.shape[-2]# 获取预计算的 cos 和 sin 值cos = self.cos_cached[:, :, :seq_len, ...]sin = self.sin_cached[:, :, :seq_len, ...]# 执行旋转# 1. 将 x 分为两半# x1.shape, x2.shape: (batch_size, num_heads, seq_len, head_dim/2)x1 = x[..., 0::2]  # 偶数维度x2 = x[..., 1::2]  # 奇数维度# 2. 应用旋转公式# x_rotated = (x1 + i*x2) * (cos + i*sin) = (x1*cos - x2*sin) + i*(x1*sin + x2*cos)rotated_x1 = x1 * cos[..., 0::2] - x2 * sin[..., 0::2]rotated_x2 = x1 * sin[..., 1::2] + x2 * cos[..., 1::2]# 3. 将旋转后的两半合并rotated_x = torch.cat([rotated_x1, rotated_x2], dim=-1)return rotated_x# --- 集成到多头注意力中 ---
class RoPEMultiHeadAttention(nn.Module):def __init__(self, d_model, num_heads, max_len=512):super().__init__()assert d_model % num_heads == 0self.d_model = d_modelself.num_heads = num_headsself.head_dim = d_model // num_headsself.q_proj = nn.Linear(d_model, d_model)self.k_proj = nn.Linear(d_model, d_model)self.v_proj = nn.Linear(d_model, d_model)self.out_proj = nn.Linear(d_model, d_model)self.rotary_encoder = RotaryPositionalEncoding(self.head_dim, max_len)def forward(self, x, mask=None):batch_size, seq_len, _ = x.shape# 1. 线性投影q = self.q_proj(x)k = self.k_proj(x)v = self.v_proj(x)# 2. 改变形状以适应多头# shape: (batch_size, num_heads, seq_len, head_dim)q = q.view(batch_size, seq_len, self.num_heads, self.head_dim).transpose(1, 2)k = k.view(batch_size, seq_len, self.num_heads, self.head_dim).transpose(1, 2)v = v.view(batch_size, seq_len, self.num_heads, self.head_dim).transpose(1, 2)# 3. 对 Q 和 K 应用旋转位置编码q = self.rotary_encoder(q)k = self.rotary_encoder(k)# 4. 计算注意力得分# scores.shape: (batch_size, num_heads, seq_len, seq_len)scores = torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(self.head_dim)if mask is not None:scores = scores.masked_fill(mask == 0, float('-inf'))attention = torch.softmax(scores, dim=-1)# 5. 应用注意力到 V# context.shape: (batch_size, num_heads, seq_len, head_dim)context = torch.matmul(attention, v)# 6. 恢复形状并进行最终投影context = context.transpose(1, 2).contiguous().view(batch_size, seq_len, self.d_model)output = self.out_proj(context)return output

RoPE 作为目前最受欢迎的位置编码,其优势如下:

良好的长度外推性:由于其相对位置的性质,RoPE 能够很好地泛化到比训练时更长的序列。
随距离增加而衰减的注意力:RoPE 的数学性质天然地使得随着相对距离的增加,注意力得分会有一个衰减的趋势,这符合语言直觉(离得越远的词关系越弱)。
高性能:它不引入额外的模型参数,并且计算非常高效,可以无缝集成到现有的自注意力框架中。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/87831.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/87831.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

pytorch学习-10.卷积神经网络(基础篇)

2.线性模型 3.梯度下降算法 4.反向传播(用pytorch算梯度) 5.用pytorch实现线性回归 6.logistic回归 7.处理多维特征的输入 8.加载数据集 9.多分类问题 10.卷积神经网络(基础篇)_哔哩哔哩_bilibili 10.1卷积神经网络 10.1.1 卷积神经网络工作流程&…

ARMv8 创建1、2、3级页表代码与注释

对下面的地址空间创建3级页表 // level 1 table, 4 entries: // 0000 0000 - 3FFF FFFF, 1GB block, DDR // 4000 0000 - 7FFF FFFF, 1GB block, DDR // 8000 0000 - BFFF FFFF, 1GB block, DDR // C000 0000 - FFFF FFFF, point to level2 tabel // // level 2 table, 512 en…

DeepSeek-R1满血版:硅基流动API或本地部署

大家好! 想在手机上部署 DeepSeek-R1 满血版(671B)?我来手把手教你最靠谱的两种方式!满血版模型参数高达 671 亿,手机本地运行几乎不可能,但通过「云服务 手机 App」的组合,你一样能在手机上丝…

React 各颜色转换方法、颜色值换算工具HEX、RGB/RGBA、HSL/HSLA、HSV、CMYK

📖 简介 基于 React Tailwind CSS 构建的专业颜色转换工具,支持多种颜色格式的实时转换。无论是设计师、开发者,都能在这个工具中找到所需的颜色转换功能。 ✨ 核心功能 🎯 多格式颜色转换 HEX 格式: 支持 3 位缩写 (#000, #…

开关电源抄板学习

一、实物 输入220V,输出12V5A 二、拍照并使用PS矫正 用卡尺测量下PCB的尺寸,在PS中作为画布。 用相机拍下照片,导入到PS中,用拉伸工具对图片进行矫正处理,并拉伸到和画布一样大小。 三、打开嘉立创EDA,导…

大数据在UI前端的应用探索:基于用户行为分析的产品优化策略

hello宝子们...我们是艾斯视觉擅长ui设计、前端开发、数字孪生、大数据、三维建模、三维动画10年经验!希望我的分享能帮助到您!如需帮助可以评论关注私信我们一起探讨!致敬感谢感恩! 一、引言:用户行为分析重构产品优化的技术逻辑 在数字化产品体验竞争日益激烈的今…

优化 WebSocket 实现单例连接用于打印【待测试 】

class PrinterWebSocket { constructor(url) { if (PrinterWebSocket.instance) { return PrinterWebSocket.instance; } this.url url; this.socket null; this.queue []; // 打印任务队列 this.isConnecting false; this.retry…

Spring Cloud Alibaba/Spring Boot整合华为云存储实例(REST API方式)

一个小作业,初次尝试华为云存储,一点分享 原项目采用Spring Cloud Alibaba微服务技术、Spring Boot框架技术、VueJS前端框架开发技术,nacos注册中心,数据库为mysql 下面看一下没有运用云存储的原项目(可跳过&#xf…

Petalinux工程如何离线编译

目录 一.下载离线包 1.1 共享状态缓存包:sstate-cache 1.1.1 进入官网打开Petalinux工具网页 1.1.2 找到相应的Petalinux版本 1.1.3 根据平台下载 1.2 下载downloads源码包 1.3 open_components源码包 二.解压 2.1 sstate-cache 2.2 downloads源码包 2.3…

w446数字化农家乐管理平台的设计与实现

🙊作者简介:多年一线开发工作经验,原创团队,分享技术代码帮助学生学习,独立完成自己的网站项目。 代码可以查看文章末尾⬇️联系方式获取,记得注明来意哦~🌹赠送计算机毕业设计600个选题excel文…

AWS WebRTC:通过shell分析viewer端日志文件

在并发过程中,每个viewer会产生一个对应的日志文件,日志文件名为: viewer_channel_index_20250626_030943_145.logviewer端日志比master端日志文件数量多,比例大概是5:1,有1个master就会有5个viewer,每个viewer对应一个日志文件。 我要统计的是从启动viewer到出第一帧视…

时间转换——借助时间模块time

两种时间戳类型 例如s11704879917000 1、13位的时间戳:单位(毫秒) (1)毫秒变成秒,1s1000ms,s1/1000(秒) (2)加载时间 times time.localtime(…

LabVIEW MathScript薄板热流模拟

热流模拟是热设计关键环节,传统工具精准但开发周期长,本 VI 利用 LabVIEW 优势,面向工程师快速验证需求,在初步方案迭代、教学演示等场景更具效率,为热分析提供轻量化替代路径,后续可结合专业工具&#xff…

为什么大语言模型训练和推理中越来越多地使用 bfloat16?

随着大语言模型(LLM)的参数规模从几十亿(B)飙升到千亿(T)级别,模型的训练与推理效率变得尤为关键。为了在保证精度的同时节省显存、加快运算,混合精度训练(Mixed Precisi…

暴力破解漏洞与命令执行漏洞

在当今的互联网世界中,网络安全威胁无处不在。对于Java后端开发者而言,了解常见的Web漏洞及其防护措施至关重要。本文将探讨两种常见的安全漏洞:暴力破解漏洞(Brute Force)和命令执行漏洞(Command Injectio…

HDFS Java API 开发指南:从基础操作到高级应用

HDFS (Hadoop Distributed File System) 作为大数据生态的核心存储系统,提供了分布式、高容错、高吞吐量的数据存储能力。通过 Java API 操作 HDFS 是开发大数据应用的基础技能。本文将基于你的笔记,详细解析 HDFS Java API 的使用方法,并提供…

区块链技术核心组件及应用架构的全面解析

区块链技术是一套融合密码学、分布式系统与经济激励的复合型技术体系,以下是其核心组件及应用架构的全面解析:一、区块链核心技术栈 1. 分布式账本技术(DLT) 核心原理:多节点共同维护不可篡改的数据链数据结构&#xf…

golang 协程 如何中断和恢复

Go语言通知协程退出(取消)的几种方式 - 知乎 GoLang之goroutine底层系列二(goroutine的创建、让出、恢复)_golang goroutine-CSDN博客 在 Go 语言中,协程(也称为 goroutine)是通过 go 关键字启动的轻量级线程。由于 goroutine 的调度是由 Go…

ARMv8 创建3级页表示例

最近在研究arm v8页表创建过程,顺带做了一个如下形式的页表, // level 1 table, 4 entries: // 0000 0000 - 3FFF FFFF, 1GB block, DDR // 4000 0000 - 7FFF FFFF, 1GB block, DDR // 8000 0000 - BFFF FFFF, 1GB block, DDR // C000 0000 - FFFF FFFF…

迁港战平 精神可胜国足

迁港战平可胜国足 江苏省城市足球联赛第6轮,宿迁队主场迎战连云港队。比赛中,宿迁队由张栋和高驰各入一球,连云港队则凭借穆家鑫与李团杰的进球连扳两城。最终双方以2比2握手言和。 第38分钟,张栋角球进攻中无人盯防推射破门&…