File size: 7,969 Bytes
271818e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 | """
FORGE-3B Data Collators.
Provides batching utilities that bridge the tokenizer output format with what
the model and training engines expect. Three collators are provided:
PretrainCollator β packs uint32 pre-tokenized sequences; no padding needed
SFTCollator β pads to max length in batch; masks prompt tokens (-100)
DPOCollator β handles (chosen, rejected) pairs with independent padding
All collators return BF16-compatible long-int tensors on CPU; the training
engine moves them to the correct device with non_blocking=True.
"""
from __future__ import annotations
import logging
from dataclasses import dataclass
from typing import Any, Dict, List, Optional
import torch
import numpy as np
logger = logging.getLogger(__name__)
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Pretrain Collator
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
@dataclass
class PretrainCollator:
"""
Collator for pre-packed, fixed-length sequences from PackedTokenDataset.
Input samples are dicts with 'input_ids' and 'labels' (torch.long tensors
of identical length seq_len). This collator simply stacks them β no padding
required because all sequences are the same length by construction.
"""
def __call__(self, samples: List[Dict[str, torch.Tensor]]) -> Dict[str, torch.Tensor]:
input_ids = torch.stack([s["input_ids"] for s in samples], dim=0) # (B, T)
labels = torch.stack([s["labels"] for s in samples], dim=0) # (B, T)
return {"input_ids": input_ids, "labels": labels}
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# SFT Collator
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
@dataclass
class SFTCollator:
"""
Collator for Supervised Fine-Tuning.
Pads variable-length sequences within a batch to the length of the longest
sequence. Prompt tokens are already masked to -100 by the dataset; this
collator propagates that masking for padded positions.
Args:
pad_token_id: token ID used for input padding (model ignores with attn mask)
max_length: hard cap β sequences longer than this are truncated
label_pad_id: value used to pad labels (default -100 = ignored by cross-entropy)
"""
pad_token_id: int = 0
max_length: int = 4096
label_pad_id: int = -100
def __call__(self, samples: List[Dict[str, torch.Tensor]]) -> Dict[str, torch.Tensor]:
input_ids_list = [s["input_ids"][: self.max_length] for s in samples]
labels_list = [s["labels"][: self.max_length] for s in samples]
max_len = max(t.size(0) for t in input_ids_list)
padded_input = torch.full((len(samples), max_len), self.pad_token_id, dtype=torch.long)
padded_labels = torch.full((len(samples), max_len), self.label_pad_id, dtype=torch.long)
attn_mask = torch.zeros((len(samples), max_len), dtype=torch.long)
for i, (ids, labs) in enumerate(zip(input_ids_list, labels_list)):
seq_len = ids.size(0)
padded_input[i, :seq_len] = ids
padded_labels[i, :seq_len] = labs
attn_mask[i, :seq_len] = 1
return {
"input_ids": padded_input,
"labels": padded_labels,
"attention_mask": attn_mask,
}
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# DPO Collator
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
@dataclass
class DPOCollator:
"""
Collator for Direct Preference Optimization.
Each sample contains four tensors:
chosen_input_ids, chosen_labels, rejected_input_ids, rejected_labels
We pad chosen and rejected independently to their respective batch maximums
so GPU memory is not wasted when chosen and rejected have very different lengths.
Args:
pad_token_id: used to pad input_ids
max_length: hard truncation cap per sequence
"""
pad_token_id: int = 0
max_length: int = 4096
label_pad_id: int = -100
def _pad_batch(
self,
ids_list: List[torch.Tensor],
labels_list: List[torch.Tensor],
):
max_len = max(t.size(0) for t in ids_list)
B = len(ids_list)
padded_ids = torch.full((B, max_len), self.pad_token_id, dtype=torch.long)
padded_labels = torch.full((B, max_len), self.label_pad_id, dtype=torch.long)
attn_mask = torch.zeros((B, max_len), dtype=torch.long)
for i, (ids, labs) in enumerate(zip(ids_list, labels_list)):
L = ids.size(0)
padded_ids[i, :L] = ids
padded_labels[i, :L] = labs
attn_mask[i, :L] = 1
return padded_ids, padded_labels, attn_mask
def __call__(self, samples: List[Dict[str, torch.Tensor]]) -> Dict[str, torch.Tensor]:
chosen_ids = [s["chosen_input_ids"][: self.max_length] for s in samples]
chosen_labs = [s["chosen_labels"][: self.max_length] for s in samples]
rej_ids = [s["rejected_input_ids"][: self.max_length] for s in samples]
rej_labs = [s["rejected_labels"][: self.max_length] for s in samples]
c_ids, c_labels, c_mask = self._pad_batch(chosen_ids, chosen_labs)
r_ids, r_labels, r_mask = self._pad_batch(rej_ids, rej_labs)
return {
"chosen_input_ids": c_ids,
"chosen_labels": c_labels,
"chosen_attention_mask": c_mask,
"rejected_input_ids": r_ids,
"rejected_labels": r_labels,
"rejected_attention_mask": r_mask,
}
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Factory
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def get_collator(
mode: str,
pad_token_id: int = 0,
max_length: int = 4096,
):
"""
Factory returning the correct collator for a given training mode.
Args:
mode: 'pretrain' | 'sft' | 'dpo'
pad_token_id: token ID used for padding inputs
max_length: maximum sequence length (hard truncation)
"""
if mode == "pretrain":
return PretrainCollator()
elif mode == "sft":
return SFTCollator(pad_token_id=pad_token_id, max_length=max_length)
elif mode == "dpo":
return DPOCollator(pad_token_id=pad_token_id, max_length=max_length)
else:
raise ValueError(
f"Unknown collator mode: '{mode}'. Expected 'pretrain', 'sft', or 'dpo'."
)
|