File size: 4,797 Bytes
6b73a07 d572bbd 6b73a07 d572bbd 6b73a07 d572bbd 6b73a07 d572bbd 6b73a07 d572bbd | 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 | """Batch collation and dynamic batching for translation training."""
from __future__ import annotations
import random
from typing import Iterator, Sequence
import torch
from torch.nn.utils.rnn import pad_sequence
from torch.utils.data import Sampler
class TranslationCollator:
"""Pad variable-length translation examples into one batch."""
def __init__(self, pad_token_id: int = 0, label_pad_token_id: int = -100):
self.pad_token_id = pad_token_id
self.label_pad_token_id = label_pad_token_id
def __call__(self, batch: Sequence[dict[str, torch.Tensor]]) -> dict[str, torch.Tensor]:
src_ids = pad_sequence(
[item["src_ids"] for item in batch],
batch_first=True,
padding_value=self.pad_token_id,
)
tgt_input_ids = pad_sequence(
[item["tgt_input_ids"] for item in batch],
batch_first=True,
padding_value=self.pad_token_id,
)
labels = pad_sequence(
[item["labels"] for item in batch],
batch_first=True,
padding_value=self.label_pad_token_id,
)
src_padding_mask = src_ids.eq(self.pad_token_id)
tgt_padding_mask = tgt_input_ids.eq(self.pad_token_id)
return {
"src_ids": src_ids,
"tgt_input_ids": tgt_input_ids,
"labels": labels,
"src_padding_mask": src_padding_mask,
"tgt_padding_mask": tgt_padding_mask,
"src_attention_mask": (~src_padding_mask).long(),
"tgt_attention_mask": (~tgt_padding_mask).long(),
"src_lens": torch.stack([item["src_len"] for item in batch]),
"tgt_lens": torch.stack([item["tgt_len"] for item in batch]),
}
class DynamicBatchSampler(Sampler[list[int]]):
"""Create batches constrained by an approximate max token budget."""
def __init__(
self,
lengths: Sequence[int | tuple[int, int]],
max_tokens_per_batch: int = 8192,
shuffle: bool = True,
drop_last: bool = False,
):
self.lengths = [max(length) if isinstance(length, tuple) else int(length) for length in lengths]
self.max_tokens_per_batch = max_tokens_per_batch
self.shuffle = shuffle
self.drop_last = drop_last
# 检查是否有超过最大token限制的序列
self._check_long_sequences()
def _check_long_sequences(self):
"""检查并警告过长的序列"""
long_seq_count = sum(1 for l in self.lengths if l > self.max_tokens_per_batch)
if long_seq_count > 0:
import warnings
warnings.warn(
f"Found {long_seq_count} sequences longer than max_tokens_per_batch "
f"({self.max_tokens_per_batch}). These will be placed in their own batches."
)
def __iter__(self) -> Iterator[list[int]]:
indices = list(range(len(self.lengths)))
if self.shuffle:
random.shuffle(indices)
indices.sort(key=lambda idx: self.lengths[idx])
batches: list[list[int]] = []
batch: list[int] = []
max_len = 0
for idx in indices:
seq_len = self.lengths[idx]
# 如果单个序列长度超过限制,单独放入一个批次
if seq_len > self.max_tokens_per_batch:
if batch:
batches.append(batch)
batch = []
max_len = 0
batches.append([idx])
continue
candidate_max_len = max(max_len, seq_len)
candidate_tokens = candidate_max_len * (len(batch) + 1)
if batch and candidate_tokens > self.max_tokens_per_batch:
batches.append(batch)
batch = []
max_len = 0
batch.append(idx)
max_len = max(max_len, seq_len)
if batch and not self.drop_last:
batches.append(batch)
if self.shuffle:
random.shuffle(batches)
yield from batches
def __len__(self) -> int:
count = 0
batch_size = 0
max_len = 0
for length in sorted(self.lengths):
# 过长的序列单独计数
if length > self.max_tokens_per_batch:
count += 1
continue
candidate_max_len = max(max_len, length)
if batch_size and candidate_max_len * (batch_size + 1) > self.max_tokens_per_batch:
count += 1
batch_size = 0
max_len = 0
batch_size += 1
max_len = max(max_len, length)
if batch_size and not self.drop_last:
count += 1
return count |