Skip to content

Commit c1150cd

Browse files
author
Artyom Kozhevnikov
committed
missing file
1 parent 3e16f2e commit c1150cd

1 file changed

Lines changed: 222 additions & 0 deletions

File tree

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
# Copyright (c) Meta Platforms, Inc. and affiliates.
2+
# All rights reserved.
3+
#
4+
# This source code is licensed under the BSD-style license found in the
5+
# LICENSE file in the root directory of this source tree.
6+
7+
from __future__ import annotations
8+
9+
from copy import deepcopy
10+
from dataclasses import dataclass
11+
from pathlib import Path
12+
from typing import Any, Final, List, Set, final, override
13+
14+
import pyarrow as pa
15+
import pyarrow.parquet as pq
16+
from torch import Tensor
17+
18+
from fairseq2.data import DataPipelineBuilder, read_sequence
19+
from fairseq2.data.parquet import (
20+
FragmentLoadingConfig,
21+
FragmentStreamingConfig,
22+
NamedColumns,
23+
ParquetFragmentLoader,
24+
ParquetFragmentStreamer,
25+
)
26+
from fairseq2.data.text.tokenizers import TextTokenEncoder
27+
from fairseq2.datasets import DataPipelineReader, DataReader, SequenceBatch, SyncMode
28+
from fairseq2.datasets.jsonl import JsonlDataset
29+
from fairseq2.datasets.text import TextDataset, TextReadOptions
30+
from fairseq2.gang import Gang
31+
from fairseq2.logging import log
32+
from fairseq2.nn import BatchLayout
33+
34+
PARQUET_TEXT_DATASET_FAMILY: Final = "parquet_text"
35+
36+
37+
@dataclass
38+
class DefaultTextSchema(NamedColumns):
39+
"""Default schema for parquet text datasets.
40+
One should pass an different `text` value here if the parquet file has a different column name.
41+
42+
Or, alternatively, one can pass it with
43+
options.extras["columns"] = DefaultTextSchema(text="my_text_column_name")
44+
"""
45+
46+
text: str = "text"
47+
extra_columns: List[str] | None = None
48+
49+
50+
class ParquetDatasetInterface:
51+
52+
_name: str
53+
_dataset: pq.ParquetDataset
54+
_splits: set[str]
55+
split_column: str = "split"
56+
57+
def __init__(self, name: str, dataset: pq.ParquetDataset, splits: set[str]) -> None:
58+
self._dataset = dataset
59+
self._splits = splits
60+
self._name = name
61+
62+
@classmethod
63+
def from_path(
64+
cls,
65+
path: Path | str | List[str | Path],
66+
name: str,
67+
filesystem: Any | None = None,
68+
) -> "ParquetDatasetInterface":
69+
70+
# from stopes.fb_config import get_filesystem_from_path
71+
# if filesystem is None:
72+
# path, filesystem = get_filesystem_from_path(path)
73+
dataset = pq.ParquetDataset(path, filesystem=filesystem) # type: ignore
74+
75+
assert isinstance(dataset, pq.ParquetDataset)
76+
partition_columns: List[str] = []
77+
if dataset.partitioning is not None:
78+
partition_columns = dataset.partitioning.schema.names
79+
80+
splits: Set[str] = set()
81+
if dataset.partitioning is not None and cls.split_column in partition_columns:
82+
idx = partition_columns.index(cls.split_column)
83+
_splits = dataset.partitioning.dictionaries[idx]
84+
if _splits is None:
85+
splits = set()
86+
else:
87+
splits = set(_splits.to_pylist())
88+
89+
return cls(name, dataset, splits)
90+
91+
def splits(self) -> set[str]:
92+
return self._splits
93+
94+
95+
@final
96+
class ParquetTextDataset(ParquetDatasetInterface, TextDataset):
97+
98+
@staticmethod
99+
def get_example_loading_builder(
100+
dataset: pq.ParquetDataset,
101+
options: TextReadOptions,
102+
split: str | None,
103+
columns: NamedColumns | None,
104+
rank: int,
105+
world_size: int,
106+
pa_cpu_count: int = 20,
107+
) -> DataPipelineBuilder:
108+
109+
npc = options.npc
110+
pa_cpu_count = int(options.extras.get("pa_cpu_count", pa_cpu_count)) # type: ignore
111+
pa.set_cpu_count(pa_cpu_count)
112+
pa.set_io_thread_count(pa_cpu_count)
113+
114+
# Streaming
115+
partition_filters = options.extras.get("partition_filters", None)
116+
parquet_files: List[str] = dataset.files # type: ignore
117+
118+
is_train_streaming = (split is not None) and (
119+
"train" in split and options.sync_mode == SyncMode.UNTIL_FIRST
120+
) # FIXME: make it configurable
121+
122+
files_circular_shift = options.extras.get("files_circular_shift", False)
123+
assert isinstance(files_circular_shift, bool)
124+
125+
fragment_shuffle_window = options.extras.get(
126+
"fragment_shuffle_window", -1 if is_train_streaming else 0
127+
)
128+
assert isinstance(fragment_shuffle_window, int)
129+
130+
fragment_config = FragmentStreamingConfig(
131+
parquet_path=parquet_files,
132+
filesystem=dataset.filesystem,
133+
nb_epochs=(None if is_train_streaming else 1),
134+
partition_filters=partition_filters, # type: ignore
135+
split_to_row_groups=True,
136+
files_circular_shift=files_circular_shift,
137+
seed=options.seed,
138+
fragment_shuffle_window=fragment_shuffle_window,
139+
)
140+
141+
if split is not None:
142+
fragment_config = fragment_config.add_partition_filter(
143+
pa.compute.field("split") == split
144+
)
145+
fragement_builder = ParquetFragmentStreamer(
146+
config=fragment_config
147+
).build_pipeline(rank=rank, world_size=world_size)
148+
149+
num_parallel_fragments = options.extras.get("num_parallel_fragments", npc)
150+
assert isinstance(num_parallel_fragments, int)
151+
assert num_parallel_fragments > 0, "num_parallel_fragments must be > 0"
152+
153+
columns = options.extras.get("columns", columns) # type: ignore
154+
assert columns is None or isinstance(columns, NamedColumns)
155+
156+
cache = options.extras.get("cache", False)
157+
assert isinstance(cache, bool)
158+
159+
add_fragment_traces = options.extras.get("add_fragment_traces", False)
160+
assert isinstance(add_fragment_traces, bool)
161+
162+
loading_config = FragmentLoadingConfig(
163+
columns=columns,
164+
add_fragment_traces=add_fragment_traces,
165+
num_parallel_fragments=num_parallel_fragments,
166+
nb_prefetch=options.num_prefetch,
167+
non_deterministic_read=True,
168+
cache=cache,
169+
drop_null=False,
170+
filters=None,
171+
)
172+
173+
# load data in memory
174+
builder = ParquetFragmentLoader(config=loading_config).apply(fragement_builder)
175+
176+
builder = builder.yield_from(
177+
lambda table: read_sequence(table.to_pylist()).and_return()
178+
)
179+
return builder
180+
181+
@override
182+
def create_reader(
183+
self,
184+
text_encoder: TextTokenEncoder,
185+
pad_idx: int | None,
186+
gang: Gang,
187+
min_seq_len: int,
188+
max_seq_len: int,
189+
options: TextReadOptions | None = None,
190+
split: str | None = None,
191+
) -> DataReader[SequenceBatch]:
192+
if options is None:
193+
options = TextReadOptions()
194+
else:
195+
options = deepcopy(options)
196+
197+
if min_seq_len > 0:
198+
log.warning(
199+
f"The `min_seq_len={min_seq_len}` is ignored for JSONL datasets because of packing."
200+
)
201+
202+
builder = ParquetTextDataset.get_example_loading_builder(
203+
self._dataset,
204+
options,
205+
split=split,
206+
columns=DefaultTextSchema(),
207+
rank=gang.rank,
208+
world_size=gang.size,
209+
)
210+
options.seed += gang.rank
211+
212+
pipeline = JsonlDataset.build_pipeline_backend(
213+
builder,
214+
options,
215+
text_encoder,
216+
pad_idx=pad_idx,
217+
max_seq_len=max_seq_len,
218+
text_column_name="text",
219+
)
220+
return DataPipelineReader[SequenceBatch](
221+
self._name, "default", pipeline, gang, options
222+
)

0 commit comments

Comments
 (0)