A Princeton Researcher Proposes Recurrent Looped Transformer (RLT) that Carries Decoder State across Every Token, Fixing 96 Blocks per Token with Unbounded Temporal Depth
Back to Tutorials
aiTutorialintermediate

A Princeton Researcher Proposes Recurrent Looped Transformer (RLT) that Carries Decoder State across Every Token, Fixing 96 Blocks per Token with Unbounded Temporal Depth

September 13, 202621 views5 min read

Learn to implement a Recurrent Looped Transformer (RLT) architecture that maintains decoder state across tokens, enabling unbounded temporal depth for long sequence modeling.

Introduction

In this tutorial, we'll explore the foundational concepts behind the Recurrent Looped Transformer (RLT) proposed by Princeton researcher Yifan Zhang. While the full implementation details and code aren't yet available, we can build a conceptual framework that demonstrates the key architectural elements of RLT - specifically how to implement a recurrent decoder that maintains state across tokens. This will help you understand how RLT's approach to unbounded temporal depth works, and how it differs from traditional transformer architectures.

Prerequisites

  • Basic understanding of transformer architectures and attention mechanisms
  • Python programming experience
  • Familiarity with PyTorch or TensorFlow
  • Knowledge of recurrent neural networks (RNNs) and their limitations
  • Understanding of tokenization and sequence processing

Step-by-Step Instructions

1. Setting up the Environment

First, we'll create a Python environment with the necessary libraries for our RLT implementation:

pip install torch transformers numpy

2. Creating the Core Transformer Components

Let's start by implementing the basic transformer blocks that will form our RLT architecture:

import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.nn import MultiheadAttention

# Basic transformer layer with attention
class TransformerLayer(nn.Module):
    def __init__(self, d_model, nhead, dim_feedforward=2048, dropout=0.1):
        super().__init__()
        self.self_attn = MultiheadAttention(d_model, nhead, dropout=dropout)
        self.linear1 = nn.Linear(d_model, dim_feedforward)
        self.linear2 = nn.Linear(dim_feedforward, d_model)
        self.norm1 = nn.LayerNorm(d_model)
        self.norm2 = nn.LayerNorm(d_model)
        self.dropout = nn.Dropout(dropout)

    def forward(self, src, src_mask=None, src_key_padding_mask=None):
        src2 = self.self_attn(src, src, src, attn_mask=src_mask,
                             key_padding_mask=src_key_padding_mask)[0]
        src = src + self.dropout(src2)
        src = self.norm1(src)
        src2 = self.linear2(F.relu(self.linear1(src)))
        src = src + self.dropout(src2)
        src = self.norm2(src)
        return src

3. Implementing the Recurrent Decoder

The key innovation in RLT is the recurrent decoder that carries its state across tokens. Here's how we'll implement it:

class RecurrentDecoder(nn.Module):
    def __init__(self, d_model, nhead, num_layers, dropout=0.1):
        super().__init__()
        self.d_model = d_model
        self.nhead = nhead
        self.num_layers = num_layers
        self.layers = nn.ModuleList([
            TransformerLayer(d_model, nhead, dropout=dropout)
            for _ in range(num_layers)
        ])
        self.dropout = nn.Dropout(dropout)
        
        # State storage for recurrent behavior
        self.decoder_state = None
        self.cache = []

    def forward(self, tgt, memory, use_cache=True):
        # Initialize or maintain state
        if self.decoder_state is None:
            self.decoder_state = torch.zeros(
                tgt.size(0), tgt.size(1), self.d_model, device=tgt.device
            )

        # Process through layers
        for i, layer in enumerate(self.layers):
            tgt = layer(tgt, src_key_padding_mask=None)
            
            # Store attention cache for sliding window
            if use_cache and i < len(self.cache):
                self.cache[i] = tgt
            elif use_cache:
                self.cache.append(tgt)
                
        # Update decoder state
        self.decoder_state = tgt
        return tgt

    def get_state(self):
        return self.decoder_state

    def reset_state(self):
        self.decoder_state = None
        self.cache = []

4. Building the RLT Architecture

Now we'll create the full RLT architecture that combines the encoder and recurrent decoder:

class RLT(nn.Module):
    def __init__(self, vocab_size, d_model=512, nhead=8, num_encoder_layers=48,
                 num_decoder_layers=48, dropout=0.1):
        super().__init__()
        self.d_model = d_model
        self.embedding = nn.Embedding(vocab_size, d_model)
        self.pos_encoding = self._get_positional_encoding(d_model)
        
        # Encoder (causal)
        self.encoder = nn.ModuleList([
            TransformerLayer(d_model, nhead, dropout=dropout)
            for _ in range(num_encoder_layers)
        ])
        
        # Recurrent Decoder
        self.decoder = RecurrentDecoder(d_model, nhead, num_decoder_layers, dropout)
        
        self.fc_out = nn.Linear(d_model, vocab_size)
        self.dropout = nn.Dropout(dropout)

    def _get_positional_encoding(self, d_model, max_len=5000):
        pe = torch.zeros(max_len, d_model)
        position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
        div_term = torch.exp(torch.arange(0, d_model, 2).float() *
                            (-torch.log(torch.tensor(10000.0)) / d_model))
        pe[:, 0::2] = torch.sin(position * div_term)
        pe[:, 1::2] = torch.cos(position * div_term)
        return pe.unsqueeze(0)

    def forward(self, src, tgt, use_cache=True):
        # Encode source
        src_emb = self.embedding(src) * torch.sqrt(torch.tensor(self.d_model, dtype=torch.float32))
        src_emb += self.pos_encoding[:, :src.size(1)]
        src_emb = self.dropout(src_emb)
        
        # Process through encoder
        for layer in self.encoder:
            src_emb = layer(src_emb)
        
        # Decode with recurrent behavior
        tgt_emb = self.embedding(tgt) * torch.sqrt(torch.tensor(self.d_model, dtype=torch.float32))
        tgt_emb += self.pos_encoding[:, :tgt.size(1)]
        tgt_emb = self.dropout(tgt_emb)
        
        output = self.decoder(tgt_emb, src_emb, use_cache=use_cache)
        output = self.fc_out(output)
        
        return output

5. Testing the RLT Implementation

Let's create a simple test to verify our RLT implementation works:

# Test the RLT implementation
vocab_size = 1000
model = RLT(vocab_size, d_model=256, nhead=4, num_encoder_layers=8, num_decoder_layers=8)

# Create sample inputs
src = torch.randint(0, vocab_size, (2, 10))  # Batch size 2, sequence length 10
 tgt = torch.randint(0, vocab_size, (2, 5))   # Batch size 2, sequence length 5

# Forward pass
output = model(src, tgt)
print(f"Output shape: {output.shape}")
print("RLT implementation test completed successfully!")

6. Understanding State Management

One of the key aspects of RLT is managing the decoder state across tokens. Here's how we can implement state tracking:

def simulate_token_generation(model, initial_input, max_tokens=20):
    """Simulate generating tokens with state persistence"""
    model.eval()
    with torch.no_grad():
        current_input = initial_input
        generated_tokens = []
        
        for i in range(max_tokens):
            # Forward pass
            output = model(current_input, current_input, use_cache=True)
            
            # Get next token (simplified)
            next_token = output.argmax(dim=-1)[:, -1].unsqueeze(1)
            generated_tokens.append(next_token)
            
            # Update input for next iteration
            current_input = torch.cat([current_input, next_token], dim=1)
            
            # Show state evolution
            if i % 5 == 0:
                print(f"Token {i} state shape: {model.decoder.get_state().shape}")
        
        return torch.cat(generated_tokens, dim=1)

Summary

This tutorial demonstrated the conceptual implementation of a Recurrent Looped Transformer (RLT) architecture. While we didn't implement the full hardware-aware execution or exact policy RL replay contract mentioned in the original paper, we've covered the core components:

  • Basic transformer layer implementation
  • Recurrent decoder that maintains state across tokens
  • Architecture that combines encoder and decoder components
  • State management for temporal depth

The RLT's innovation lies in its ability to maintain decoder state and attention cache across tokens, enabling unbounded temporal depth without resetting at serving boundaries. This approach addresses the limitation of traditional transformers that process sequences in fixed blocks, making it particularly promising for applications requiring long-term dependencies and continuous generation.

While this is a conceptual implementation, it provides a foundation for understanding how RLT's architecture could be extended with actual hardware-aware optimizations and reinforcement learning components as described in the original research.

Source: MarkTechPost

Related Articles