chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,235 @@
|
||||
"""
|
||||
---
|
||||
title: Rotary Positional Embeddings (RoPE)
|
||||
summary: >
|
||||
Annotated implementation of RoPE from paper
|
||||
RoFormer: Enhanced Transformer with Rotary Position Embedding
|
||||
---
|
||||
|
||||
# Rotary Positional Embeddings (RoPE)
|
||||
|
||||
This is an implementation of
|
||||
[Rotary Positional Embeddings (RoPE)](https://arxiv.org/abs/2104.09864)
|
||||
in [PyTorch](https://pytorch.org).
|
||||
|
||||
Rotary Positional Embeddings (RoPE) encode position information of tokens
|
||||
with a rotation matrix that naturally incorporates explicit relative position
|
||||
dependency.
|
||||
|
||||
Here's [the training code](experiment.html) for training a transformer model with RoPE
|
||||
on Tiny Shakespeare dataset.
|
||||
"""
|
||||
|
||||
import torch
|
||||
from torch import nn
|
||||
|
||||
from labml.logger import inspect
|
||||
from labml_nn.transformers.mha import MultiHeadAttention
|
||||
|
||||
|
||||
class RotaryPositionalEmbeddings(nn.Module):
|
||||
"""
|
||||
## RoPE module
|
||||
|
||||
Rotary encoding transforms pairs of features by rotating in the 2D plane.
|
||||
That is, it organizes the $d$ features as $\frac{d}{2}$ pairs.
|
||||
Each pair can be considered a coordinate in a 2D plane, and the encoding will rotate it
|
||||
by an angle depending on the position of the token.
|
||||
|
||||
### For a pair of features
|
||||
|
||||
Let $x^{(1)}_m$ and $x^{(2)}_m$ be two features of the
|
||||
key or query of any head at position $m$.
|
||||
Or for simplicity assume $x$ has only two features.
|
||||
Then the transformation is,
|
||||
|
||||
\begin{align}
|
||||
RoPE\big(x^{(1)}_m, x^{(2)}_m, m\big) &=
|
||||
\begin{pmatrix}
|
||||
\cos m \theta & - \sin m \theta \\
|
||||
\sin m \theta & \cos m \theta
|
||||
\end{pmatrix}
|
||||
\begin{pmatrix}
|
||||
x^{(1)}_m \\
|
||||
x^{(2)}_m \\
|
||||
\end{pmatrix} \\
|
||||
&=
|
||||
\begin{pmatrix}
|
||||
x^{(1)}_m \cos m\theta - x^{(2)}_m \sin m \theta \\
|
||||
x^{(2)}_m \cos m\theta + x^{(1)}_m \sin m \theta \\
|
||||
\end{pmatrix} \\
|
||||
\end{align}
|
||||
|
||||
where $\theta$ is a constant angle. The other pairs of features are transformed similarly.
|
||||
|
||||
### Attention is relative
|
||||
|
||||
For a pair of features, dot-product attention score between two positions $m$ and $n$ would be
|
||||
|
||||
\begin{align}
|
||||
\Big \langle RoPE\big(x^{(1)}_m, x^{(2)}_m, m\big), RoPE\big(x^{(1)}_n, x^{(2)}_n, n\big) \Big \rangle &= \\
|
||||
(x^{(1)}_m \cos m\theta - x^{(2)}_m \sin m \theta)(x^{(1)}_n \cos n\theta - x^{(2)}_n \sin n \theta) &+ \\
|
||||
(x^{(2)}_m \cos m\theta + x^{(1)}_m \sin m \theta)(x^{(2)}_n \cos n\theta + x^{(1)}_n \sin n \theta) &= \\
|
||||
x^{(1)}_m x^{(1)}_n (\cos m\theta \cos n\theta + \sin m \theta \sin n \theta) &+ \\
|
||||
x^{(1)}_m x^{(2)}_n (-\cos m\theta \sin n\theta + \sin m \theta \cos n \theta) &+ \\
|
||||
x^{(2)}_m x^{(1)}_n (-\sin m\theta \cos n\theta + \cos m \theta \sin n \theta) &+ \\
|
||||
x^{(2)}_m x^{(2)}_n (\sin m\theta \sin n\theta + \cos m \theta \cos n \theta) &= \\
|
||||
|
||||
x^{(1)}_m x^{(1)}_n \cos (m - n) \theta +
|
||||
x^{(1)}_m x^{(2)}_n \sin(m - n) \theta &+ \\
|
||||
- x^{(2)}_m x^{(1)}_n \sin (m - n) \theta +
|
||||
x^{(2)}_m x^{(2)}_n \cos (m - n) \theta &= \\
|
||||
|
||||
\big(x^{(1)}_m \cos (m - n)\theta - x^{(2)}_m \sin (m - n) \theta\big) x^{(1)}_n &+ \\
|
||||
\big(x^{(2)}_m \cos (m - n)\theta + x^{(1)}_m \sin (m - n) \theta\big) x^{(2)}_n &= \\
|
||||
|
||||
\Big \langle RoPE\big(x^{(1)}_m, x^{(2)}_m, m - n\big), RoPE\big(x^{(1)}_n, x^{(2)}_n, 0\big) \Big \rangle
|
||||
\end{align}
|
||||
|
||||
This shows that for dot-production attention the rotary encodings gives relative attention.
|
||||
|
||||
### For all features
|
||||
|
||||
The features are grouped into pairs and handled as above. They use a different $\theta$ for each pair.
|
||||
|
||||
The paper suggests using $\Theta = {\theta_i = 10000^{\frac{2(i-1)}{d}}, i \in [1, 2, ..., \frac{d}{2}]}$
|
||||
for the $\frac{d}{2}$ pairs of features.
|
||||
|
||||
The original implementation of RoPE divide the $d$-dimension features into $\frac{d}{2}$ pairs of features ($i$, $i + 1$).
|
||||
In this implementation we pair feature $i$ with feature $i + \frac{d}{2}$. So for position $m$ we transform
|
||||
|
||||
\begin{align}
|
||||
\begin{pmatrix}
|
||||
x^{(i)}_m \\
|
||||
x^{(i + \frac{d}{2})}_m
|
||||
\end{pmatrix}
|
||||
\end{align}
|
||||
|
||||
to
|
||||
|
||||
\begin{align}
|
||||
\begin{pmatrix}
|
||||
x^{(i)}_m \cos m \theta_i - x^{(i + \frac{d}{2})}_m \sin m \theta_i \\
|
||||
x^{(i + \frac{d}{2})}_m \cos m\theta_i + x^{(i)}_m \sin m \theta_i \\
|
||||
\end{pmatrix} \\
|
||||
\end{align}
|
||||
"""
|
||||
|
||||
def __init__(self, d: int, base: int = 10_000):
|
||||
"""
|
||||
* `d` is the number of features $d$
|
||||
* `base` is the constant used for calculating $\Theta$
|
||||
"""
|
||||
super().__init__()
|
||||
|
||||
self.base = base
|
||||
self.d = d
|
||||
self.cos_cached = None
|
||||
self.sin_cached = None
|
||||
|
||||
def _build_cache(self, x: torch.Tensor):
|
||||
"""
|
||||
Cache $\cos$ and $\sin$ values
|
||||
"""
|
||||
# Return if cache is already built
|
||||
if self.cos_cached is not None and x.shape[0] <= self.cos_cached.shape[0]:
|
||||
return
|
||||
|
||||
# Get sequence length
|
||||
seq_len = x.shape[0]
|
||||
|
||||
# $\Theta = {\theta_i = 10000^{-\frac{2(i-1)}{d}}, i \in [1, 2, ..., \frac{d}{2}]}$
|
||||
theta = 1. / (self.base ** (torch.arange(0, self.d, 2).float() / self.d)).to(x.device)
|
||||
|
||||
# Create position indexes `[0, 1, ..., seq_len - 1]`
|
||||
seq_idx = torch.arange(seq_len, device=x.device).float().to(x.device)
|
||||
|
||||
# Calculate the product of position index and $\theta_i$
|
||||
idx_theta = torch.einsum('n,d->nd', seq_idx, theta)
|
||||
|
||||
# Concatenate so that for row $m$ we have
|
||||
# $[m \theta_0, m \theta_1, ..., m \theta_{\frac{d}{2}}, m \theta_0, m \theta_1, ..., m \theta_{\frac{d}{2}}]$
|
||||
idx_theta2 = torch.cat([idx_theta, idx_theta], dim=1)
|
||||
|
||||
# Cache them
|
||||
self.cos_cached = idx_theta2.cos()[:, None, None, :]
|
||||
self.sin_cached = idx_theta2.sin()[:, None, None, :]
|
||||
|
||||
def _neg_half(self, x: torch.Tensor):
|
||||
# $\frac{d}{2}$
|
||||
d_2 = self.d // 2
|
||||
|
||||
# Calculate $[-x^{(\frac{d}{2} + 1)}, -x^{(\frac{d}{2} + 2)}, ..., -x^{(d)}, x^{(1)}, x^{(2)}, ..., x^{(\frac{d}{2})}]$
|
||||
return torch.cat([-x[:, :, :, d_2:], x[:, :, :, :d_2]], dim=-1)
|
||||
|
||||
def forward(self, x: torch.Tensor):
|
||||
"""
|
||||
* `x` is the Tensor at the head of a key or a query with shape `[seq_len, batch_size, n_heads, d]`
|
||||
"""
|
||||
# Cache $\cos$ and $\sin$ values
|
||||
self._build_cache(x)
|
||||
|
||||
# Sequence length
|
||||
seq_len = x.shape[0]
|
||||
|
||||
# Split the features, we can choose to apply rotary embeddings only to a partial set of features.
|
||||
x_rope, x_pass = x[..., :self.d], x[..., self.d:]
|
||||
|
||||
# Calculate
|
||||
# $[-x^{(\frac{d}{2} + 1)}, -x^{(\frac{d}{2} + 2)}, ..., -x^{(d)}, x^{(1)}, x^{(2)}, ..., x^{(\frac{d}{2})}]$
|
||||
neg_half_x = self._neg_half(x_rope)
|
||||
|
||||
# Calculate
|
||||
#
|
||||
# \begin{align}
|
||||
# \begin{pmatrix}
|
||||
# x^{(i)}_m \cos m \theta_i - x^{(i + \frac{d}{2})}_m \sin m \theta_i \\
|
||||
# x^{(i + \frac{d}{2})}_m \cos m\theta_i + x^{(i)}_m \sin m \theta_i \\
|
||||
# \end{pmatrix} \\
|
||||
# \end{align}
|
||||
#
|
||||
# for $i \in {1, 2, ..., \frac{d}{2}}$
|
||||
x_rope = (x_rope * self.cos_cached[:seq_len]) + (neg_half_x * self.sin_cached[:seq_len])
|
||||
|
||||
#
|
||||
return torch.cat((x_rope, x_pass), dim=-1)
|
||||
|
||||
|
||||
class RotaryPEMultiHeadAttention(MultiHeadAttention):
|
||||
"""
|
||||
## Multi-head attention with rotary positional embeddings
|
||||
|
||||
We override [multi-head attention from original transformer](../mha.html).
|
||||
"""
|
||||
|
||||
def __init__(self, heads: int, d_model: int, rope_percentage: float = 0.5, dropout_prob: float = 0.0):
|
||||
super().__init__(heads, d_model, dropout_prob)
|
||||
|
||||
# Rotary positional embedding layers
|
||||
d_rope = int(self.d_k * rope_percentage)
|
||||
self.query_rotary_pe = RotaryPositionalEmbeddings(d_rope)
|
||||
self.key_rotary_pe = RotaryPositionalEmbeddings(d_rope)
|
||||
|
||||
def get_scores(self, query: torch.Tensor, key: torch.Tensor):
|
||||
"""
|
||||
### Calculate scores between queries and keys
|
||||
"""
|
||||
|
||||
# Calculate dot-product with RoPE
|
||||
return torch.einsum('ibhd,jbhd->ijbh', self.query_rotary_pe(query), self.key_rotary_pe(key))
|
||||
|
||||
|
||||
def _test_rotary():
|
||||
"""
|
||||
Testing RoPE with a simple example
|
||||
"""
|
||||
x = torch.tensor([[1, 2, 3, 4], [4, 5, 6, 7], [7, 8, 9, 10]], dtype=torch.float)
|
||||
x = x[:, None, None, :]
|
||||
inspect(x)
|
||||
|
||||
rotary_pe = RotaryPositionalEmbeddings(4)
|
||||
inspect(rotary_pe(x))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
_test_rotary()
|
||||
@@ -0,0 +1,102 @@
|
||||
"""
|
||||
---
|
||||
title: Rotary Positional Embeddings (RoPE) Experiment
|
||||
summary: This experiment trains a transformer model with Rotary Positional Embeddings (RoPE) on tiny Shakespeare dataset.
|
||||
---
|
||||
|
||||
# Rotary Positional Embeddings (RoPE) Experiment
|
||||
|
||||
This is an annotated PyTorch experiment to train a transformer model with Rotary Positional Embeddings (RoPE).
|
||||
"""
|
||||
|
||||
from labml import experiment
|
||||
from labml.configs import option, calculate
|
||||
from labml_nn.transformers import TransformerConfigs
|
||||
from labml_nn.transformers.basic.autoregressive_experiment import AutoregressiveTransformer, Configs
|
||||
|
||||
|
||||
# ### Rotary PE attention
|
||||
def _rotary_pe_mha(c: TransformerConfigs):
|
||||
from labml_nn.transformers.rope import RotaryPEMultiHeadAttention
|
||||
return RotaryPEMultiHeadAttention(c.n_heads, c.d_model, 1.)
|
||||
|
||||
|
||||
# Configuration options
|
||||
calculate(TransformerConfigs.encoder_attn, 'rotary', _rotary_pe_mha)
|
||||
calculate(TransformerConfigs.decoder_attn, 'rotary', _rotary_pe_mha)
|
||||
calculate(TransformerConfigs.decoder_mem_attn, 'rotary', _rotary_pe_mha)
|
||||
|
||||
|
||||
@option(Configs.model, 'rotary_pe_transformer')
|
||||
def _model(c: Configs):
|
||||
"""
|
||||
Create an autoregressive model and initialize weights
|
||||
"""
|
||||
m = AutoregressiveTransformer(c.transformer.encoder,
|
||||
c.transformer.src_embed,
|
||||
c.transformer.generator).to(c.device)
|
||||
|
||||
return m
|
||||
|
||||
|
||||
def main():
|
||||
# Create experiment
|
||||
experiment.create(name="rotary_pe_transformer", writers={'screen'})
|
||||
# Create configs
|
||||
conf = Configs()
|
||||
# Override configurations
|
||||
experiment.configs(conf, {
|
||||
# No fixed positional embeddings
|
||||
'transformer.src_embed': 'no_pos',
|
||||
'transformer.tgt_embed': 'no_pos',
|
||||
|
||||
# Encoder with RoPE
|
||||
'transformer.encoder_attn': 'rotary',
|
||||
|
||||
#
|
||||
'model': 'rotary_pe_transformer',
|
||||
|
||||
# Use character level tokenizer
|
||||
'tokenizer': 'character',
|
||||
# Prompt separator is blank
|
||||
'prompt_separator': '',
|
||||
# Starting prompt for sampling
|
||||
'prompt': 'It is ',
|
||||
# Use Tiny Shakespeare dataset
|
||||
'text': 'tiny_shakespeare',
|
||||
|
||||
# Use a context size of $256$
|
||||
'seq_len': 512,
|
||||
# Train for 32 epochs
|
||||
'epochs': 32,
|
||||
# Batch size $4$
|
||||
'batch_size': 4,
|
||||
# Switch between training and validation for $10$ times
|
||||
# per epoch
|
||||
'inner_iterations': 10,
|
||||
|
||||
# Model size
|
||||
'd_model': 128,
|
||||
'transformer.ffn.d_ff': 512,
|
||||
'transformer.n_heads': 16,
|
||||
'transformer.dropout': 0.0,
|
||||
|
||||
# Use [Noam optimizer](../../optimizers/noam.html)
|
||||
'optimizer.optimizer': 'Noam',
|
||||
'optimizer.learning_rate': 1.,
|
||||
|
||||
'dataloader_shuffle_with_replacement': True
|
||||
})
|
||||
|
||||
# Set models for saving and loading
|
||||
experiment.add_pytorch_models({'model': conf.model})
|
||||
|
||||
# Start the experiment
|
||||
with experiment.start():
|
||||
# Run training
|
||||
conf.run()
|
||||
|
||||
|
||||
#
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,246 @@
|
||||
"""
|
||||
---
|
||||
title: Rotary Positional Embeddings with Relative distance (RoPER)
|
||||
summary: >
|
||||
This is an implementation of RoPER which adds relative distance information to embeddings on
|
||||
top of RoPE introduced in RoFormer: Enhanced Transformer with Rotary Position Embedding
|
||||
---
|
||||
|
||||
*RoPER is work by [Georges Harik (@gharik)](https://twitter.com/gharik),
|
||||
and this implementation is based on his original code.*
|
||||
|
||||
# Rotary Positional Embeddings with Relative distance (RoPER)
|
||||
|
||||
[Rotary Positional Embeddings (RoPE)](https://arxiv.org/abs/2104.09864) includes
|
||||
relative positions in attention score calculation.
|
||||
However, the embeddings themselves do not get any positional information
|
||||
, [except what it can get implicitly from causal attention](https://arxiv.org/abs/2c364684b15b11ecac827bce58715ee7).
|
||||
|
||||
RoPER adds relative positional information explicitly to value embeddings.
|
||||
Specifically, it adds the relative positions of the tokens it paid attention to.
|
||||
We use same rotary positional embeddings to rotate the values in attention,
|
||||
Then, after taking the weighted sum,
|
||||
we rotate the final in the opposite direction.
|
||||
Which is equivalent to rotating each of the values (before attention) relative to the current position.
|
||||
|
||||
Here's [the training code](experiment.html) for training a transformer model with RoPER
|
||||
on an arithmetic addition where we can see significant improvement over RoPE.
|
||||
|
||||
### Relative distances in embeddings
|
||||
|
||||
For any head, let $a_{n,i}$ be the attention from position $n$ to position $i$,
|
||||
and $v_i$ be the value embeddings at position $i$. Let's denote individual features
|
||||
as $v^{(1)}_i, v^{(2)}_i, \dots$.
|
||||
|
||||
Normally, we would take the weight sum of value embeddings
|
||||
|
||||
$$o^{(j)}_n = \sum_i a_{n,i} v^{(j)}_i$$
|
||||
|
||||
This doesn't explicitly add any distance information about the positions $i$ to final
|
||||
result $o^{(j)}_n$.
|
||||
|
||||
RoPER pairs features like RoPE and transform them.
|
||||
For a pair $v^{(1)}_m$ and $v^{(2)}_m$ it transforms them by
|
||||
$RoPE\big(v^{(1)}_m, v^{(2)}_m, m\big)$.
|
||||
Let us donate the transformed features with $\hat{v}^{(1)}_m, \hat{v}^{(2)}_m$.
|
||||
Then it rotates the weighted sum $\hat{o}^{(j)}_n$ in the the reverse direction with
|
||||
$RoPE\big(\hat{o}^{(1)}_n, \hat{o}^{(2)}_n, -n\big)$.
|
||||
*Note the *$-n$.
|
||||
|
||||
Note that,
|
||||
|
||||
\begin{align}
|
||||
RoPE\big(x^{(1)}_m, x^{(2)}_m, m\big) &=
|
||||
\begin{pmatrix}
|
||||
\cos m \theta & - \sin m \theta \\
|
||||
\sin m \theta & \cos m \theta
|
||||
\end{pmatrix}
|
||||
\begin{pmatrix}
|
||||
x^{(1)}_m \\
|
||||
x^{(2)}_m \\
|
||||
\end{pmatrix} \\
|
||||
&=
|
||||
\begin{pmatrix}
|
||||
x^{(1)}_m \cos m\theta - x^{(2)}_m \sin m \theta \\
|
||||
x^{(2)}_m \cos m\theta + x^{(1)}_m \sin m \theta \\
|
||||
\end{pmatrix} \\
|
||||
\end{align}
|
||||
|
||||
Final output after with the transformations is,
|
||||
|
||||
\begin{align}
|
||||
RoPE\big(\hat{o}^{(1)}_n, \hat{o}^{(2)}_n, -n\big) &= \\
|
||||
\begin{pmatrix}
|
||||
\hat{o}^{(1)}_n \cos n\theta + \hat{o}^{(2)}_n \sin n \theta \\
|
||||
\hat{o}^{(2)}_n \cos n\theta - \hat{o}^{(1)}_n \sin n \theta \\
|
||||
\end{pmatrix} \\
|
||||
\end{align}
|
||||
|
||||
*Note that *$\sin (-n \theta) = -\sin n \theta$.
|
||||
|
||||
Let's expand the first term $\hat{o}^{(1)}_n \cos n\theta + \hat{o}^{(2)}_n \sin n \theta$,
|
||||
|
||||
\begin{align}
|
||||
\hat{o}^{(1)}_n \cos n\theta + \hat{o}^{(2)}_n \sin n \theta &= \\
|
||||
\sum_i a_{n,i} \hat{v}^{(1)}_i \cos n\theta + \sum_i a_{n,i} \hat{v}^{(2)}_i \sin n \theta &= \\
|
||||
|
||||
\sum_i a_{n,i} \Big( v^{(1)}_i \cos i\theta - v^{(2)}_i \sin i \theta \Big) \cos n\theta &+ \\
|
||||
\sum_i a_{n,i} \Big( v^{(2)}_i \cos i\theta + v^{(1)}_i \sin i \theta \Big) \sin m \theta &= \\
|
||||
|
||||
\sum_i a_{n,i} v^{(1)}_i \Big( \cos i\theta \cos n\theta + \sin i \theta \sin n \theta \Big) &+ \\
|
||||
\sum_i a_{n,i} v^{(2)}_i \Big( \cos i\theta \sin n\theta - \sin i \theta \cos n \theta \Big) &= \\
|
||||
|
||||
\sum_i a_{n,i} v^{(1)}_i \cos (i - n) \theta - \sum_i a_{n,i} v^{(2)}_i \sin (i - n) \theta &= \\
|
||||
|
||||
\sum_i a_{n,i} v^{(1)}_i \cos (i - n) \theta - \sum_i a_{n,i} v^{(2)}_i \sin (i - n) \theta
|
||||
\end{align}
|
||||
|
||||
Simiarly we can show the second term is equal to,
|
||||
|
||||
$$\sum_i a_{n,i} v^{(1)}_i \cos (i - n) \theta + \sum_i a_{n,i} v^{(2)}_i \sin (i - n) \theta$$
|
||||
|
||||
Which gives,
|
||||
|
||||
\begin{align}
|
||||
RoPE\big(\hat{o}^{(1)}_n, \hat{o}^{(2)}_n, -n\big) &= \\
|
||||
\begin{pmatrix}
|
||||
\sum_i a_{n,i} v^{(1)}_i \cos (i - n) \theta - \sum_i a_{n,i} v^{(2)}_i \sin (i - n) \theta \\
|
||||
\sum_i a_{n,i} v^{(1)}_i \cos (i - n) \theta + \sum_i a_{n,i} v^{(2)}_i \sin (i - n) \theta \\
|
||||
\end{pmatrix} &= \\
|
||||
\sum_i a_{n,i} RoPE \big (v^{(1)}_i, v^{(1)}_i, (i - n) \theta \big)
|
||||
\end{align}
|
||||
|
||||
That is, the weighted average of values rotated relative to current position.
|
||||
|
||||
[Here's an experiment](arithmetic_experiment.html) that uses RoPER on an arthmetic addition task.
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
import torch
|
||||
|
||||
from labml_nn.transformers.rope import RotaryPositionalEmbeddings, RotaryPEMultiHeadAttention
|
||||
|
||||
|
||||
class ReverseRotaryPositionalEmbeddings(RotaryPositionalEmbeddings):
|
||||
"""
|
||||
## RoPE module that rotates in the opposite direction
|
||||
|
||||
This inherits from [RoPE rotation implementation](../index.html) and changes the direction.
|
||||
"""
|
||||
|
||||
def forward(self, x: torch.Tensor):
|
||||
"""
|
||||
* `x` is the Tensor at the head of a key or a query with shape `[seq_len, batch_size, n_heads, d]`
|
||||
"""
|
||||
# Cache $\cos$ and $\sin$ values
|
||||
self._build_cache(x)
|
||||
|
||||
# Split the features, we can choose to apply rotary embeddings only to a partial set of features.
|
||||
x_rope, x_pass = x[..., :self.d], x[..., self.d:]
|
||||
|
||||
# Calculate
|
||||
# $[-x^{(\frac{d}{2} + 1)}, -x^{(\frac{d}{2} + 2)}, ..., -x^{(d)}, x^{(1)}, x^{(2)}, ..., x^{(\frac{d}{2})}]$
|
||||
neg_half_x = self._neg_half(x_rope)
|
||||
|
||||
# Calculate
|
||||
#
|
||||
# \begin{align}
|
||||
# \begin{pmatrix}
|
||||
# x^{(i)}_m \cos -m \theta_i - x^{(i + \frac{d}{2})}_m \sin -m \theta_i \\
|
||||
# x^{(i + \frac{d}{2})}_m \cos -m\theta_i + x^{(i)}_m \sin -m \theta_i \\
|
||||
# \end{pmatrix} = \\
|
||||
# \begin{pmatrix}
|
||||
# x^{(i)}_m \cos m \theta_i + x^{(i + \frac{d}{2})}_m \sin m \theta_i \\
|
||||
# x^{(i + \frac{d}{2})}_m \cos m\theta_i - x^{(i)}_m \sin m \theta_i \\
|
||||
# \end{pmatrix} \\
|
||||
# \end{align}
|
||||
#
|
||||
# for $i \in {1, 2, ..., \frac{d}{2}}$
|
||||
x_rope = (x_rope * self.cos_cached[:x.shape[0]]) - (neg_half_x * self.sin_cached[:x.shape[0]])
|
||||
|
||||
#
|
||||
return torch.cat((x_rope, x_pass), dim=-1)
|
||||
|
||||
|
||||
class RotaryValuePEMultiHeadAttention(RotaryPEMultiHeadAttention):
|
||||
"""
|
||||
## Multi-head attention with rotary positional embeddings
|
||||
|
||||
We override [multi-head attention from original transformer](../mha.html).
|
||||
"""
|
||||
|
||||
def __init__(self, heads: int, d_model: int,
|
||||
rope_percentage: float = 0.5, rope_value_percentage: float = 0.5,
|
||||
dropout_prob: float = 0.0):
|
||||
super().__init__(heads, d_model, rope_percentage, dropout_prob)
|
||||
|
||||
# Rotary positional embedding layers
|
||||
d_rope_value = int(self.d_k * rope_value_percentage)
|
||||
|
||||
self.value_rotary_pe = RotaryPositionalEmbeddings(d_rope_value)
|
||||
self.value_reverse_rotary_pe = ReverseRotaryPositionalEmbeddings(d_rope_value)
|
||||
|
||||
def forward(self, *,
|
||||
query: torch.Tensor,
|
||||
key: torch.Tensor,
|
||||
value: torch.Tensor,
|
||||
mask: Optional[torch.Tensor] = None):
|
||||
"""
|
||||
`query`, `key` and `value` are the tensors that store
|
||||
collection of *query*, *key* and *value* vectors.
|
||||
They have shape `[seq_len, batch_size, d_model]`.
|
||||
|
||||
`mask` has shape `[seq_len, seq_len, batch_size]` and
|
||||
`mask[i, j, b]` indicates whether for batch `b`,
|
||||
query at position `i` has access to key-value at position `j`.
|
||||
"""
|
||||
|
||||
# `query`, `key` and `value` have shape `[seq_len, batch_size, d_model]`
|
||||
seq_len, batch_size, _ = query.shape
|
||||
|
||||
if mask is not None:
|
||||
mask = self.prepare_mask(mask, query.shape, key.shape)
|
||||
|
||||
# Prepare `query`, `key` and `value` for attention computation.
|
||||
# These will then have shape `[seq_len, batch_size, heads, d_k]`.
|
||||
query = self.query(query)
|
||||
key = self.key(key)
|
||||
value = self.value(value)
|
||||
|
||||
# Compute attention scores $Q K^\top$.
|
||||
# This gives a tensor of shape `[seq_len, seq_len, batch_size, heads]`.
|
||||
scores = self.get_scores(query, key)
|
||||
|
||||
# Scale scores $\frac{Q K^\top}{\sqrt{d_k}}$
|
||||
scores *= self.scale
|
||||
|
||||
# Apply mask
|
||||
if mask is not None:
|
||||
scores = scores.masked_fill(mask == 0, float('-inf'))
|
||||
|
||||
# $softmax$ attention along the key sequence dimension
|
||||
# $\underset{seq}{softmax}\Bigg(\frac{Q K^\top}{\sqrt{d_k}}\Bigg)$
|
||||
attn = self.softmax(scores)
|
||||
|
||||
# Apply dropout
|
||||
attn = self.dropout(attn)
|
||||
|
||||
# Rotate value embeddings before taking the weighted sum so that they contain positional information
|
||||
value = self.value_rotary_pe(value)
|
||||
|
||||
# Multiply by values
|
||||
# $$\underset{seq}{softmax}\Bigg(\frac{Q K^\top}{\sqrt{d_k}}\Bigg)V$$
|
||||
x = torch.einsum("ijbh,jbhd->ibhd", attn, value)
|
||||
|
||||
# Rotate in the opposite direction so that each embedding hold the relative positions
|
||||
x = self.value_reverse_rotary_pe(x)
|
||||
|
||||
# Save attentions for any other calculations
|
||||
self.attn = attn.detach()
|
||||
|
||||
# Concatenate multiple heads
|
||||
x = x.reshape(seq_len, batch_size, -1)
|
||||
|
||||
# Output layer
|
||||
return self.output(x)
|
||||
@@ -0,0 +1,93 @@
|
||||
"""
|
||||
---
|
||||
title: Rotary Positional Embeddings with Relative distance (RoPER) Experiment
|
||||
summary: This experiment trains a transformer model with Rotary Positional Embeddings with
|
||||
Relative Distance (RoPER) on the arithmetic addition task.
|
||||
---
|
||||
|
||||
# Rotary Positional Embeddings with Relative distance ([RoPER](index.html)) Experiment
|
||||
"""
|
||||
|
||||
from labml import experiment
|
||||
from labml.configs import calculate
|
||||
from labml_nn.experiments.arithmetic_dataset import ArithmeticAutoregression
|
||||
from labml_nn.transformers import TransformerConfigs
|
||||
from labml_nn.transformers.rope.experiment import Configs as RoPEConfigs
|
||||
|
||||
|
||||
class Configs(RoPEConfigs, ArithmeticAutoregression):
|
||||
"""
|
||||
We inherit [RoPE experiment](../experiment.html) and use it for
|
||||
[arithmetic addition task](../../experiments/arithmetic_dataset.html).
|
||||
|
||||
We add the option to change attention to use Rotary Positional Embeddings with Relative distance (RoPER)
|
||||
below.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
def _rotary_value_pe_mha(c: TransformerConfigs):
|
||||
"""
|
||||
Use Rotary Positional Embeddings with Relative distance ([RoPER](index.html)) in attention.
|
||||
"""
|
||||
from labml_nn.transformers.rope.value_pe import RotaryValuePEMultiHeadAttention
|
||||
return RotaryValuePEMultiHeadAttention(c.n_heads, c.d_model, 1., 1.)
|
||||
|
||||
|
||||
# Configuration options
|
||||
calculate(TransformerConfigs.encoder_attn, 'rotary_value', _rotary_value_pe_mha)
|
||||
calculate(TransformerConfigs.decoder_attn, 'rotary_value', _rotary_value_pe_mha)
|
||||
calculate(TransformerConfigs.decoder_mem_attn, 'rotary_value', _rotary_value_pe_mha)
|
||||
|
||||
|
||||
def main():
|
||||
# Create experiment
|
||||
experiment.create(name="roper_addition", comment="rotary value 7", writers={'screen', 'labml'})
|
||||
# Create configs
|
||||
conf = Configs()
|
||||
# Override configurations
|
||||
experiment.configs(conf, {
|
||||
'max_digits': 7,
|
||||
|
||||
# No fixed positional embeddings
|
||||
'transformer.src_embed': 'no_pos',
|
||||
'transformer.tgt_embed': 'no_pos',
|
||||
|
||||
# Encoder with RoPER attention
|
||||
'transformer.encoder_attn': 'rotary_value',
|
||||
# Encoder with RoPE attention
|
||||
# 'transformer.encoder_attn': 'rotary',
|
||||
|
||||
#
|
||||
'model': 'rotary_pe_transformer',
|
||||
|
||||
# Use a context size of $256$
|
||||
'seq_len': 512,
|
||||
# Train for 32 epochs
|
||||
'epochs': 20,
|
||||
# Batch size $4$
|
||||
'batch_size': 16,
|
||||
|
||||
# Model size
|
||||
'd_model': 128,
|
||||
'transformer.ffn.d_ff': 512,
|
||||
'transformer.n_heads': 4,
|
||||
'transformer.dropout': 0.0,
|
||||
|
||||
# Use [Adam optimizer](../../optimizers/noam.html)
|
||||
'optimizer.optimizer': 'Adam',
|
||||
'optimizer.learning_rate': 2.5e-4,
|
||||
})
|
||||
|
||||
# Set models for saving and loading
|
||||
experiment.add_pytorch_models({'model': conf.model})
|
||||
|
||||
# Start the experiment
|
||||
with experiment.start():
|
||||
# Run training
|
||||
conf.run()
|
||||
|
||||
|
||||
#
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,96 @@
|
||||
"""
|
||||
---
|
||||
title: Rotary Positional Embeddings (RoPE) Experiment
|
||||
summary: This experiment trains a transformer model with Rotary Positional Embeddings (RoPE) on tiny Shakespeare dataset.
|
||||
---
|
||||
|
||||
# Rotary Positional Embeddings (RoPE) Experiment
|
||||
|
||||
This is an annotated PyTorch experiment to train a transformer model with Rotary Positional Embeddings (RoPE).
|
||||
"""
|
||||
|
||||
from labml import experiment
|
||||
from labml.configs import calculate
|
||||
from labml_nn.transformers import TransformerConfigs
|
||||
from labml_nn.transformers.rope.experiment import Configs as RoPEConfigs
|
||||
|
||||
|
||||
# ### Rotary PE attention
|
||||
|
||||
class Configs(RoPEConfigs): # , ArithmeticAutoregression):
|
||||
pass
|
||||
|
||||
|
||||
def _rotary_value_pe_mha(c: TransformerConfigs):
|
||||
from labml_nn.transformers.rope.value_pe import RotaryValuePEMultiHeadAttention
|
||||
return RotaryValuePEMultiHeadAttention(c.n_heads, c.d_model, 1., 1.)
|
||||
|
||||
|
||||
# Configuration options
|
||||
calculate(TransformerConfigs.encoder_attn, 'rotary_value', _rotary_value_pe_mha)
|
||||
calculate(TransformerConfigs.decoder_attn, 'rotary_value', _rotary_value_pe_mha)
|
||||
calculate(TransformerConfigs.decoder_mem_attn, 'rotary_value', _rotary_value_pe_mha)
|
||||
|
||||
|
||||
def main():
|
||||
# Create experiment
|
||||
experiment.create(name="rotary_shakespeare", comment="rotary value", writers={'screen', 'labml'})
|
||||
# Create configs
|
||||
conf = Configs()
|
||||
# Override configurations
|
||||
experiment.configs(conf, {
|
||||
# No fixed positional embeddings
|
||||
'transformer.src_embed': 'no_pos',
|
||||
'transformer.tgt_embed': 'no_pos',
|
||||
|
||||
# Encoder with RoPE
|
||||
'transformer.encoder_attn': 'rotary_value',
|
||||
# 'transformer.encoder_attn': 'rotary',
|
||||
|
||||
#
|
||||
'model': 'rotary_pe_transformer',
|
||||
|
||||
# Use character level tokenizer
|
||||
'tokenizer': 'character',
|
||||
# Prompt separator is blank
|
||||
'prompt_separator': '',
|
||||
# Starting prompt for sampling
|
||||
'prompt': 'It is ',
|
||||
# Use Tiny Shakespeare dataset
|
||||
'text': 'tiny_shakespeare',
|
||||
|
||||
# Use a context size of $256$
|
||||
'seq_len': 512,
|
||||
# Train for 32 epochs
|
||||
'epochs': 24,
|
||||
# Batch size $4$
|
||||
'batch_size': 16,
|
||||
# Switch between training and validation for $10$ times
|
||||
# per epoch
|
||||
'inner_iterations': 4,
|
||||
|
||||
# Model size
|
||||
'd_model': 128,
|
||||
'transformer.ffn.d_ff': 512,
|
||||
'transformer.n_heads': 4,
|
||||
'transformer.dropout': 0.0,
|
||||
|
||||
# Use [Adam optimizer](../../optimizers/noam.html)
|
||||
'optimizer.optimizer': 'Adam',
|
||||
'optimizer.learning_rate': 2.5e-4,
|
||||
|
||||
'dataloader_shuffle_with_replacement': True
|
||||
})
|
||||
|
||||
# Set models for saving and loading
|
||||
experiment.add_pytorch_models({'model': conf.model})
|
||||
|
||||
# Start the experiment
|
||||
with experiment.start():
|
||||
# Run training
|
||||
conf.run()
|
||||
|
||||
|
||||
#
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user