|
14 | 14 | from torch import Tensor |
15 | 15 | from typing_extensions import override |
16 | 16 |
|
17 | | -from fairseq2.data import ( |
18 | | - DataPipeline, |
19 | | - read_sequence, |
20 | | -) |
| 17 | +from fairseq2.data import DataPipeline, DataPipelineBuilder, read_sequence |
21 | 18 | from fairseq2.data.text import read_text |
22 | 19 | from fairseq2.data.text.tokenizers import TextTokenEncoder |
23 | 20 | from fairseq2.datasets import ( |
|
27 | 24 | LengthBatching, |
28 | 25 | SequenceBatch, |
29 | 26 | ) |
30 | | -from fairseq2.datasets.text import TextDataset, TextReadOptions |
| 27 | +from fairseq2.datasets.text import GenericTextDataset, TextDataset, TextReadOptions |
| 28 | +from fairseq2.device import Device |
31 | 29 | from fairseq2.error import NotSupportedError |
32 | 30 | from fairseq2.gang import Gang |
| 31 | +from fairseq2.logging import log |
33 | 32 | from fairseq2.nn import BatchLayout |
34 | 33 |
|
35 | | -# TODO: FIX, INFER |
36 | | -npc = 10 |
37 | | - |
38 | | - |
39 | 34 | JSONL_DATASET_FAMILY: Final = "jsonl" |
40 | 35 |
|
41 | 36 |
|
@@ -75,63 +70,109 @@ def create_reader( |
75 | 70 | min_seq_len: int, |
76 | 71 | max_seq_len: int, |
77 | 72 | options: TextReadOptions | None = None, |
| 73 | + split: str | None = None, |
78 | 74 | ) -> DataReader[SequenceBatch]: |
79 | 75 | if options is None: |
80 | 76 | options = TextReadOptions() |
81 | 77 |
|
82 | 78 | file_rank = gang.rank |
83 | | - |
84 | 79 | file_world_size = gang.size |
85 | 80 |
|
86 | | - if len(self._files) < file_world_size: |
| 81 | + text_column_name = options.extras.get("text_column_name", "text") |
| 82 | + assert isinstance(text_column_name, str) |
| 83 | + |
| 84 | + if min_seq_len > 0: |
| 85 | + log.warning( |
| 86 | + f"The `min_seq_len={min_seq_len}` is ignored because of packing." |
| 87 | + ) |
| 88 | + |
| 89 | + split_pattern = options.extras.get("split_pattern", None) |
| 90 | + split_files = GenericTextDataset.filter_split( |
| 91 | + self._files, |
| 92 | + split, |
| 93 | + extention="jsonl", |
| 94 | + split_pattern=split_pattern, # type: ignore[arg-type] |
| 95 | + ) |
| 96 | + |
| 97 | + if len(split_files) < file_world_size: |
87 | 98 | raise NotSupportedError( |
88 | 99 | "The number of dataset files must be greater than or equal to the number of world size." |
89 | 100 | ) |
90 | 101 |
|
91 | | - builder = read_sequence(self._files) |
| 102 | + builder = read_sequence(split_files) |
92 | 103 |
|
93 | 104 | if file_world_size > 1: |
94 | 105 | builder.shard(file_rank, file_world_size, allow_uneven=True) |
95 | 106 |
|
96 | 107 | def read_file(file: Path) -> DataPipeline: |
97 | | - return read_text(file).map(json.loads, num_parallel_calls=1).and_return() |
| 108 | + return read_text(file).map(json.loads).and_return() |
98 | 109 |
|
99 | 110 | builder.yield_from(read_file) |
100 | 111 |
|
| 112 | + pipeline = JsonlDataset.build_pipeline_backend( |
| 113 | + builder, |
| 114 | + options, |
| 115 | + text_encoder, |
| 116 | + pad_idx=pad_idx, |
| 117 | + max_seq_len=max_seq_len, |
| 118 | + text_column_name=text_column_name, |
| 119 | + device=gang.device, |
| 120 | + ) |
| 121 | + return DataPipelineReader[SequenceBatch]( |
| 122 | + self._name, "default", pipeline, gang, options |
| 123 | + ) |
| 124 | + |
| 125 | + @staticmethod |
| 126 | + def build_pipeline_backend( |
| 127 | + builder: DataPipelineBuilder, |
| 128 | + options: TextReadOptions, |
| 129 | + text_encoder: TextTokenEncoder, |
| 130 | + max_seq_len: int, |
| 131 | + pad_idx: int | None, |
| 132 | + text_column_name: str, |
| 133 | + device: Device, |
| 134 | + ) -> DataPipeline: |
| 135 | + if pad_idx is None: |
| 136 | + pad_idx = 0 |
| 137 | + |
| 138 | + seed = options.seed |
| 139 | + if options.example_shuffle_window != 1: |
| 140 | + builder.shuffle(options.example_shuffle_window, seed) |
| 141 | + |
101 | 142 | # Tokenize. |
102 | 143 | def encode(example: dict[str, Any]) -> Tensor: |
103 | | - return text_encoder(example["text"]) |
| 144 | + return text_encoder(example[text_column_name]) |
104 | 145 |
|
105 | | - builder.map(encode, num_parallel_calls=1) |
| 146 | + builder.map(encode) |
106 | 147 |
|
107 | 148 | batching = options.batching |
108 | 149 |
|
109 | 150 | if isinstance(batching, LengthBatching): |
110 | 151 | max_num_elements = batching.max_num_elements |
111 | 152 |
|
| 153 | + pinned_memory = device.type == "cuda" |
| 154 | + # Pack. |
112 | 155 | builder.pack( |
113 | | - max_num_elements + 1, max_seq_len, truncate=True, pinned_memory=True |
| 156 | + max_num_elements + 1, |
| 157 | + max_seq_len, |
| 158 | + pad_value=pad_idx, |
| 159 | + truncate=True, |
| 160 | + pinned_memory=pinned_memory, |
114 | 161 | ) |
| 162 | + BatchLayout.compiled_max_seq_len = max_seq_len |
115 | 163 | else: |
116 | 164 | raise NotSupportedError(f"`{batching}` is not supported.") |
117 | 165 |
|
118 | | - BatchLayout.compiled_max_seq_len = max_seq_len |
119 | | - |
120 | 166 | # Return only the first `max_num_batches`. |
121 | 167 | if options.max_num_batches is not None: |
122 | 168 | builder.take(options.max_num_batches) |
123 | 169 |
|
124 | | - # Prefetch `num_prefetch` batches in background. |
125 | | - builder.prefetch(options.num_prefetch) |
126 | | - |
127 | 170 | # Convert to `SequenceBatch`. |
128 | 171 | def to_batch(example: dict[str, Any]) -> SequenceBatch: |
129 | 172 | seqs, seq_lens = example["seqs"], example["seq_lens"] |
130 | 173 |
|
131 | 174 | return SequenceBatch(seqs, seq_lens, packed=True) |
132 | 175 |
|
133 | | - pipeline = builder.map(to_batch).and_return() |
| 176 | + pipeline = builder.map(to_batch).prefetch(options.num_prefetch).and_return() |
134 | 177 |
|
135 | | - return DataPipelineReader[SequenceBatch]( |
136 | | - self._name, "default", pipeline, gang, options |
137 | | - ) |
| 178 | + return pipeline |
0 commit comments