RoBERTa › Implementing RoBERTa-style Training
Language AI HandbookAll chapters RoBERTa › Implementing RoBERTa-style TrainingOne section · Sections in this chapter8 / 17 ↓
Reading tools Definitions & inline links
Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.
Article links
Make inline references clickable
Let's implement the key differences between BERT and RoBERTa training. We'll focus on the data loading and masking pipeline, which captures the most important changes. Understanding the implementation concretely will reinforce the conceptual points from earlier sections: you'll see exactly how dynamic masking differs from static masking in code, how the full-sentence packing eliminates sentence boundaries, and how the training objective simplifies without NSP.
from typing import List, Tuple
import torch
class RoBERTaDataCollator:
"""
Data collator for RoBERTa-style pretraining.
Key differences from BERT:
1. No NSP - just MLM on contiguous text
2. Dynamic masking applied fresh each batch
3. Full sentences without artificial sentence boundaries
"""
def __init__(
self,
vocab_size: int,
mask_token_id: int,
pad_token_id: int,
special_token_ids: List[int],
mask_prob: float = 0.15,
):
self.vocab_size = vocab_size
self.mask_token_id = mask_token_id
self.pad_token_id = pad_token_id
self.special_token_ids = set(special_token_ids)
self.mask_prob = mask_prob
def __call__(
self, token_ids: torch.Tensor
) -> Tuple[torch.Tensor, torch.Tensor]:
"""
Apply dynamic masking to a batch of sequences.
Args:
token_ids: Shape (batch_size, seq_len)
Returns:
masked_ids: Input with masking applied
labels: Original tokens at masked positions, -100 elsewhere
"""
batch_size, seq_len = token_ids.shape
labels = token_ids.clone()
masked_ids = token_ids.clone()
# Create mask for special tokens (CLS, SEP, PAD) - never mask these
special_mask = torch.zeros_like(token_ids, dtype=torch.bool)
for special_id in self.special_token_ids:
special_mask |= token_ids == special_id
# Probability matrix - 0 for special tokens, mask_prob elsewhere
probability_matrix = torch.full(token_ids.shape, self.mask_prob)
probability_matrix[special_mask] = 0.0
# Sample positions to mask
masked_indices = torch.bernoulli(probability_matrix).bool()
# Set labels: -100 for non-masked positions (ignored in loss)
labels[~masked_indices] = -100
# 80% of masked tokens -> [MASK]
indices_replaced = (
torch.bernoulli(torch.full(token_ids.shape, 0.8)).bool()
& masked_indices
)
masked_ids[indices_replaced] = self.mask_token_id
# 10% of masked tokens -> random token
indices_random = (
torch.bernoulli(torch.full(token_ids.shape, 0.5)).bool()
& masked_indices
& ~indices_replaced
)
random_tokens = torch.randint(
self.vocab_size, token_ids.shape, dtype=token_ids.dtype
)
masked_ids[indices_random] = random_tokens[indices_random]
# Remaining 10% stay unchanged
return masked_ids, labels# Demonstrate the collator
collator = RoBERTaDataCollator(
vocab_size=50265, # RoBERTa vocab size
mask_token_id=50264, # <mask> token
pad_token_id=1, # <pad> token
special_token_ids=[0, 1, 2], # <s>, </s>, <pad>
mask_prob=0.15,
)
# Sample batch of token IDs
torch.manual_seed(42)
sample_batch = torch.randint(100, 1000, (4, 20)) # 4 sequences, 20 tokens each
# Apply dynamic masking
masked_batch, labels = collator(sample_batch)Original batch (first sequence): [142, 267, 476, 914, 226, 435, 620, 924, 950, 813, 878, 914, 810, 154, 631, 572, 615, 995, 967, 806] Masked batch (first sequence): [142, 267, 476, 914, 226, 435, 620, 924, 950, 813, 878, 914, 810, 154, 50264, 572, 615, 995, 967, 50264] Labels (first sequence): [-100, -100, -100, -100, -100, -100, -100, 924, -100, -100, -100, -100, -100, -100, 631, -100, -100, -100, -100, 806] Masked tokens: 3 / 20 (15.0%)
Now let's implement the full-sentences input format that RoBERTa uses instead of sentence pairs:
class FullSentenceDataset:
"""
Dataset that packs contiguous text into sequences without NSP.
Unlike BERT which uses sentence pairs, RoBERTa packs as much
contiguous text as possible into each sequence.
"""
def __init__(
self,
documents: List[List[int]], # List of tokenized documents
max_seq_len: int = 512,
cls_token_id: int = 0,
sep_token_id: int = 2,
):
self.max_seq_len = max_seq_len
self.cls_token_id = cls_token_id
self.sep_token_id = sep_token_id
# Flatten documents into sequences of max_seq_len
self.examples = self._create_examples(documents)
def _create_examples(
self, documents: List[List[int]]
) -> List[torch.Tensor]:
"""Pack documents into fixed-length sequences."""
examples = []
current_chunk = []
current_length = 0
# Reserve space for [CLS] and [SEP]
target_length = self.max_seq_len - 2
for doc in documents:
for token_id in doc:
current_chunk.append(token_id)
current_length += 1
if current_length >= target_length:
# Create example: [CLS] tokens [SEP]
example = (
[self.cls_token_id]
+ current_chunk[:target_length]
+ [self.sep_token_id]
)
examples.append(torch.tensor(example))
current_chunk = []
current_length = 0
return examples
def __len__(self):
return len(self.examples)
def __getitem__(self, idx):
return self.examples[idx]# Demonstrate full-sentence packing
sample_docs = [
list(range(100, 150)), # Document 1: 50 tokens
list(range(200, 280)), # Document 2: 80 tokens
list(range(300, 400)), # Document 3: 100 tokens
]
dataset = FullSentenceDataset(
documents=sample_docs,
max_seq_len=64, # Short for demonstration
cls_token_id=0,
sep_token_id=2,
)Created 3 examples from 3 documents Each example is 64 tokens First example (showing first 20 tokens): [0, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118]
The first example starts with the [CLS] token (ID 0), contains packed text from the documents, and would end with [SEP] (ID 2). The key insight is that RoBERTa's input format is simpler than BERT's. No segment embeddings needed for NSP, no alternating sentence A and B. Just pack as much contiguous text as possible and apply MLM.
The FullSentenceDataset implementation reveals a subtle efficiency point: by packing tokens greedily up to the maximum sequence length, you ensure that almost no sequence is padded. Every position in the input tensor is occupied by a real token. This is important for training efficiency because padded positions are skipped during the loss computation, so padding wastes both memory and computation. BERT's sentence-pair format often required padding to equalize lengths within a batch, since individual sentences vary widely in length. RoBERTa's full-sentence packing naturally produces sequences of uniform length, maximizing GPU utilization.