chugr 0.2.0.dev0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- chugr/__init__.py +46 -0
- chugr/app/test.py +105 -0
- chugr/common/__init__.py +9 -0
- chugr/common/cli.py +234 -0
- chugr/common/collate.py +104 -0
- chugr/common/config.py +335 -0
- chugr/common/random.py +76 -0
- chugr/common/serialize.py +150 -0
- chugr/common/task_config.py +24 -0
- chugr/common/types.py +122 -0
- chugr/common/urls.py +88 -0
- chugr/doc/__init__.py +17 -0
- chugr/doc/constants.py +15 -0
- chugr/doc/doc_processor.py +248 -0
- chugr/doc/doc_read_processor.py +249 -0
- chugr/doc/doc_vqa_processor.py +113 -0
- chugr/doc/synth_text.py +212 -0
- chugr/hfds/__init__.py +2 -0
- chugr/hfds/collate.py +83 -0
- chugr/hfds/loader.py +385 -0
- chugr/hfds/wrappers.py +108 -0
- chugr/image/__init__.py +4 -0
- chugr/image/build_transforms_doc.py +427 -0
- chugr/image/build_transforms_image.py +203 -0
- chugr/image/transforms_alb.py +112 -0
- chugr/image/transforms_factory.py +58 -0
- chugr/image/transforms_torch.py +137 -0
- chugr/loader.py +249 -0
- chugr/naflex.py +357 -0
- chugr/task_pipeline/__init__.py +7 -0
- chugr/task_pipeline/pipeline_doc_read.py +66 -0
- chugr/task_pipeline/pipeline_doc_vqa.py +124 -0
- chugr/task_pipeline/pipeline_factory.py +32 -0
- chugr/task_pipeline/pipeline_gtparse.py +93 -0
- chugr/task_pipeline/pipeline_image_text.py +90 -0
- chugr/task_pipeline/pipeline_manual.py +27 -0
- chugr/text/__init__.py +1 -0
- chugr/text/tokenization.py +222 -0
- chugr/version.py +1 -0
- chugr/wds/__init__.py +7 -0
- chugr/wds/dataset_info.py +65 -0
- chugr/wds/decode.py +319 -0
- chugr/wds/filters.py +142 -0
- chugr/wds/helpers.py +68 -0
- chugr/wds/loader.py +187 -0
- chugr/wds/pipeline.py +182 -0
- chugr/wds/shardlists.py +134 -0
- chugr/wds/tariterators.py +70 -0
- chugr-0.2.0.dev0.dist-info/METADATA +436 -0
- chugr-0.2.0.dev0.dist-info/RECORD +53 -0
- chugr-0.2.0.dev0.dist-info/WHEEL +4 -0
- chugr-0.2.0.dev0.dist-info/entry_points.txt +4 -0
- chugr-0.2.0.dev0.dist-info/licenses/LICENSE +201 -0
chugr/hfds/loader.py
ADDED
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
import math
|
|
2
|
+
import warnings
|
|
3
|
+
from copy import deepcopy
|
|
4
|
+
from typing import Callable, List, Optional
|
|
5
|
+
|
|
6
|
+
import torch
|
|
7
|
+
from torch.utils.data import DataLoader, Sampler
|
|
8
|
+
|
|
9
|
+
from chugr.common import (
|
|
10
|
+
LoaderBundle, DistributedCfg, SharedCount, rank_seed, seed_worker, shuffled_interval_indices)
|
|
11
|
+
|
|
12
|
+
from .collate import HfCollate
|
|
13
|
+
from .wrappers import SafeDataset, WrappedIterableDataset
|
|
14
|
+
|
|
15
|
+
_SAMPLE_SHUFFLE_SIZE = 2000
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class IntervalSampler(Sampler):
|
|
19
|
+
""" Shuffled, rank sharded sampler w/ an optional per-interval sample limit (for intervals < 1 epoch).
|
|
20
|
+
|
|
21
|
+
Each interval draws a fresh permutation (seed + interval), samples are consumed sequentially across
|
|
22
|
+
intervals so that short intervals still cycle through the full dataset.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
dataset,
|
|
28
|
+
num_samples: Optional[int] = None,
|
|
29
|
+
rank: int = 0,
|
|
30
|
+
world_size: int = 1,
|
|
31
|
+
seed: int = 0,
|
|
32
|
+
interval: int = 0,
|
|
33
|
+
):
|
|
34
|
+
self.dataset_len = len(dataset)
|
|
35
|
+
self.world_size = world_size
|
|
36
|
+
self.rank = rank
|
|
37
|
+
self.seed = seed
|
|
38
|
+
self.interval = interval
|
|
39
|
+
total = min(num_samples, self.dataset_len) if num_samples else self.dataset_len
|
|
40
|
+
self.num_samples_per_rank = total // world_size
|
|
41
|
+
|
|
42
|
+
def set_epoch(self, interval: int):
|
|
43
|
+
self.interval = interval
|
|
44
|
+
|
|
45
|
+
def __len__(self):
|
|
46
|
+
return self.num_samples_per_rank
|
|
47
|
+
|
|
48
|
+
def __iter__(self):
|
|
49
|
+
per_interval = self.num_samples_per_rank * self.world_size
|
|
50
|
+
indices = shuffled_interval_indices(self.dataset_len, per_interval, self.interval, self.seed)
|
|
51
|
+
return iter(indices[self.rank::self.world_size])
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class EvalSampler(Sampler):
|
|
55
|
+
""" Rank strided eval sampler, every sample exactly once across ranks w/o padding.
|
|
56
|
+
|
|
57
|
+
Unlike DistributedSampler (drop_last=False), samples are not repeated to even out the per-rank counts (ranks
|
|
58
|
+
differ by at most one sample). Repeats would be counted twice in the gathered metrics and depend on the world
|
|
59
|
+
size. Eval has no per batch collectives, so uneven per-rank batch counts are fine.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
def __init__(self, dataset, rank: int = 0, world_size: int = 1):
|
|
63
|
+
self.indices = range(rank, len(dataset), world_size)
|
|
64
|
+
|
|
65
|
+
def __len__(self):
|
|
66
|
+
return len(self.indices)
|
|
67
|
+
|
|
68
|
+
def __iter__(self):
|
|
69
|
+
return iter(self.indices)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _disable_decode(ds):
|
|
73
|
+
import datasets
|
|
74
|
+
|
|
75
|
+
to_set = []
|
|
76
|
+
for k, v in ds.features.items():
|
|
77
|
+
if isinstance(v, datasets.Image):
|
|
78
|
+
d = deepcopy(v)
|
|
79
|
+
d.decode = False
|
|
80
|
+
to_set.append((k, d))
|
|
81
|
+
elif isinstance(v, datasets.Audio):
|
|
82
|
+
d = deepcopy(v)
|
|
83
|
+
d.decode = False
|
|
84
|
+
to_set.append((k, d))
|
|
85
|
+
for k, d in to_set:
|
|
86
|
+
ds = ds.cast_column(k, d)
|
|
87
|
+
return ds
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def create_loader_hf(
|
|
91
|
+
source: str,
|
|
92
|
+
split: str,
|
|
93
|
+
task_pipeline: Optional[List[Callable]] = None,
|
|
94
|
+
data_dir: Optional[str] = None,
|
|
95
|
+
num_samples: Optional[int] = None,
|
|
96
|
+
streaming: bool = False,
|
|
97
|
+
is_training: bool = False,
|
|
98
|
+
batch_size: Optional[int] = 1,
|
|
99
|
+
resampled: bool = False,
|
|
100
|
+
multi_interval: bool = True,
|
|
101
|
+
num_batches_round: str = 'ceil',
|
|
102
|
+
num_workers: int = 4,
|
|
103
|
+
persistent_workers: bool = True,
|
|
104
|
+
start_interval: int = 0,
|
|
105
|
+
seed: int = 0,
|
|
106
|
+
collate_fn: Optional[Callable] = None,
|
|
107
|
+
sample_shuffle_size: int = _SAMPLE_SHUFFLE_SIZE,
|
|
108
|
+
distributed: DistributedCfg = DistributedCfg(),
|
|
109
|
+
disable_decode: bool = False,
|
|
110
|
+
naflex_factory: Optional[Callable] = None,
|
|
111
|
+
handler: Optional[Callable] = None,
|
|
112
|
+
):
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
source:
|
|
117
|
+
split:
|
|
118
|
+
task_pipeline:
|
|
119
|
+
data_dir:
|
|
120
|
+
num_samples:
|
|
121
|
+
streaming:
|
|
122
|
+
is_training:
|
|
123
|
+
batch_size:
|
|
124
|
+
resampled:
|
|
125
|
+
multi_interval:
|
|
126
|
+
num_batches_round:
|
|
127
|
+
num_workers:
|
|
128
|
+
persistent_workers:
|
|
129
|
+
start_interval:
|
|
130
|
+
seed:
|
|
131
|
+
collate_fn:
|
|
132
|
+
sample_shuffle_size:
|
|
133
|
+
distributed:
|
|
134
|
+
disable_decode:
|
|
135
|
+
naflex_factory: num_samples (per interval) -> (NaFlexBatchScheduler, per seq_len processors, image key),
|
|
136
|
+
train w/ schedule driven (variable seq_len & batch size) NaFlex batching, replaces the task pipeline
|
|
137
|
+
and fixed size batching
|
|
138
|
+
handler: Exception handler for NaFlex batching (failed samples are replaced)
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
|
|
142
|
+
"""
|
|
143
|
+
from datasets import VerificationMode, load_dataset
|
|
144
|
+
batched = batch_size is not None and batch_size >= 0
|
|
145
|
+
naflex = is_training and naflex_factory is not None
|
|
146
|
+
|
|
147
|
+
if naflex:
|
|
148
|
+
pass # the NaFlex dataset wrapper processes and collates
|
|
149
|
+
elif collate_fn is not None:
|
|
150
|
+
assert task_pipeline is None, 'task_pipeline should not be set if custom collation function is used.'
|
|
151
|
+
elif batched or task_pipeline is not None:
|
|
152
|
+
# collation fn applies task pipeline
|
|
153
|
+
assert task_pipeline is not None, 'task_pipeline is needed'
|
|
154
|
+
collate_fn = HfCollate(
|
|
155
|
+
task_pipeline,
|
|
156
|
+
apply_collate=batched,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
if streaming:
|
|
160
|
+
from datasets.distributed import split_dataset_by_node
|
|
161
|
+
|
|
162
|
+
dataset = load_dataset(
|
|
163
|
+
source,
|
|
164
|
+
data_dir=data_dir,
|
|
165
|
+
streaming=True,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
if split not in dataset:
|
|
169
|
+
assert False, f'Split {split} not in dataset ({dataset.keys()})'
|
|
170
|
+
dataset = dataset[split]
|
|
171
|
+
if disable_decode:
|
|
172
|
+
dataset = _disable_decode(dataset)
|
|
173
|
+
|
|
174
|
+
if not is_training and num_samples:
|
|
175
|
+
# eval, cap the stream before the rank split, the count is exact and the eval set doesn't change w/ world
|
|
176
|
+
# size. NOTE w/ dataloader workers, HF splits the cap across the workers' shards, so which samples
|
|
177
|
+
# depends on num_workers (the first num_samples of the stream w/o workers)
|
|
178
|
+
dataset = dataset.take(num_samples)
|
|
179
|
+
|
|
180
|
+
# FIXME num_samples calc, get a reliable estimate from dataset in streaming mode
|
|
181
|
+
if num_samples is None:
|
|
182
|
+
info = dataset.info
|
|
183
|
+
if info.splits is not None and split in info.splits:
|
|
184
|
+
num_samples = info.splits[split].num_examples
|
|
185
|
+
|
|
186
|
+
if is_training and multi_interval:
|
|
187
|
+
assert num_samples, (
|
|
188
|
+
"num_samples must be available in dataset metadata or manually provided for multi-interval training")
|
|
189
|
+
|
|
190
|
+
if is_training:
|
|
191
|
+
dataset = dataset.shuffle(seed, buffer_size=sample_shuffle_size)
|
|
192
|
+
|
|
193
|
+
# FIXME split_dataset_by_node has some concerns as currently implemented
|
|
194
|
+
dataset = split_dataset_by_node(dataset, distributed.global_rank, distributed.world_size)
|
|
195
|
+
interval_count = SharedCount(start_interval)
|
|
196
|
+
|
|
197
|
+
if is_training:
|
|
198
|
+
# HF assigns the data shards (files) of this rank to the dataloader workers, a worker w/o a shard
|
|
199
|
+
# yields nothing, which a fixed number of batches per worker can't tolerate
|
|
200
|
+
num_shards = getattr(dataset, 'num_shards', None) or getattr(dataset, 'n_shards', None)
|
|
201
|
+
if num_shards and num_workers > num_shards:
|
|
202
|
+
warnings.warn(
|
|
203
|
+
f'Reducing num_workers from {num_workers} to {num_shards}, the number of data shards '
|
|
204
|
+
f'of the streaming dataset (per rank).')
|
|
205
|
+
num_workers = num_shards
|
|
206
|
+
num_workers_nonzero = max(num_workers, 1)
|
|
207
|
+
# worker seeds (augmentation) differ across ranks, the dataset shuffle uses the shared seed
|
|
208
|
+
dl_generator = torch.Generator()
|
|
209
|
+
dl_generator.manual_seed(rank_seed(seed, distributed.global_rank))
|
|
210
|
+
loader_kwargs = dict(
|
|
211
|
+
sampler=None,
|
|
212
|
+
shuffle=False,
|
|
213
|
+
num_workers=num_workers,
|
|
214
|
+
persistent_workers=persistent_workers and num_workers > 0,
|
|
215
|
+
generator=dl_generator,
|
|
216
|
+
worker_init_fn=seed_worker,
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
mean_batch_size = None
|
|
220
|
+
if naflex:
|
|
221
|
+
# schedule driven batching, every worker yields its share of the rank identical schedule from its
|
|
222
|
+
# (repeating) share of the stream
|
|
223
|
+
from chugr.naflex import NaFlexBatcher
|
|
224
|
+
|
|
225
|
+
assert num_samples, 'NaFlex batching requires num_samples (per interval).'
|
|
226
|
+
scheduler, processors, image_key = naflex_factory(num_samples)
|
|
227
|
+
batcher = NaFlexBatcher(
|
|
228
|
+
scheduler, processors, image_key=image_key, interval=interval_count, handler=handler)
|
|
229
|
+
dataset = WrappedIterableDataset(dataset, interval_count=interval_count, repeat=True, stages=[batcher])
|
|
230
|
+
base_loader = DataLoader(dataset=dataset, batch_size=None, **loader_kwargs)
|
|
231
|
+
num_batches = scheduler.num_batches_for_workers(num_workers_nonzero)
|
|
232
|
+
num_samples = scheduler.num_samples_for_workers(num_workers_nonzero)
|
|
233
|
+
mean_batch_size = scheduler.mean_batch_size_for_workers(num_workers_nonzero)
|
|
234
|
+
else:
|
|
235
|
+
num_samples_per_worker = None
|
|
236
|
+
num_batches = 0
|
|
237
|
+
if is_training and batched and batch_size and num_samples:
|
|
238
|
+
# the same number of full batches on every rank, enforced by a fixed number of samples per
|
|
239
|
+
# worker w/ the stream wrapping around (as the wds loader, see the notes there)
|
|
240
|
+
round_fn = math.floor if num_batches_round == 'floor' else math.ceil
|
|
241
|
+
global_batch_size = batch_size * distributed.world_size
|
|
242
|
+
num_batches = round_fn(num_samples / global_batch_size)
|
|
243
|
+
num_batches_per_worker = max(round_fn(num_batches / num_workers_nonzero), 1)
|
|
244
|
+
num_batches = num_batches_per_worker * num_workers_nonzero
|
|
245
|
+
num_samples = num_batches * global_batch_size
|
|
246
|
+
num_samples_per_worker = num_batches_per_worker * batch_size
|
|
247
|
+
elif num_samples:
|
|
248
|
+
# eval (or unbatched), an estimate, split_dataset_by_node gives each rank ~num_samples / world_size
|
|
249
|
+
num_batches = math.ceil(num_samples / distributed.world_size / (batch_size or 1))
|
|
250
|
+
dataset = WrappedIterableDataset(
|
|
251
|
+
dataset,
|
|
252
|
+
interval_count=interval_count,
|
|
253
|
+
num_samples=num_samples_per_worker,
|
|
254
|
+
repeat=num_samples_per_worker is not None,
|
|
255
|
+
)
|
|
256
|
+
# HF datasets treats batch_size differently than torch defaults, in torch batch_size = None
|
|
257
|
+
# disables batching, in HF it returns the full dataset. We restore torch behaviour here.
|
|
258
|
+
base_loader = DataLoader(
|
|
259
|
+
dataset=dataset,
|
|
260
|
+
collate_fn=collate_fn,
|
|
261
|
+
drop_last=batched and is_training,
|
|
262
|
+
batch_size=batch_size,
|
|
263
|
+
**loader_kwargs,
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
loader = LoaderBundle(
|
|
267
|
+
loader=base_loader,
|
|
268
|
+
num_batches=num_batches,
|
|
269
|
+
num_samples=num_samples,
|
|
270
|
+
shared_interval=interval_count,
|
|
271
|
+
mean_batch_size=mean_batch_size,
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
else:
|
|
275
|
+
# split may include slicing, e.g. 'train[:1000]' or 'validation[:10%]'
|
|
276
|
+
dataset = load_dataset(
|
|
277
|
+
source,
|
|
278
|
+
data_dir=data_dir,
|
|
279
|
+
split=split,
|
|
280
|
+
verification_mode=VerificationMode.BASIC_CHECKS,
|
|
281
|
+
)
|
|
282
|
+
if not is_training and num_samples and num_samples < len(dataset):
|
|
283
|
+
dataset = dataset.select(range(num_samples))
|
|
284
|
+
if disable_decode:
|
|
285
|
+
dataset = _disable_decode(dataset)
|
|
286
|
+
if naflex:
|
|
287
|
+
return _create_loader_hf_naflex(
|
|
288
|
+
dataset,
|
|
289
|
+
naflex_factory=naflex_factory,
|
|
290
|
+
num_samples=num_samples,
|
|
291
|
+
num_workers=num_workers,
|
|
292
|
+
persistent_workers=persistent_workers,
|
|
293
|
+
start_interval=start_interval,
|
|
294
|
+
seed=seed,
|
|
295
|
+
distributed=distributed,
|
|
296
|
+
handler=handler,
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
dataset = SafeDataset(dataset)
|
|
300
|
+
|
|
301
|
+
if is_training:
|
|
302
|
+
# shuffled (per interval), sharded by rank, optionally limited to num_samples per interval
|
|
303
|
+
sampler = IntervalSampler(
|
|
304
|
+
dataset,
|
|
305
|
+
num_samples=num_samples,
|
|
306
|
+
rank=distributed.global_rank,
|
|
307
|
+
world_size=distributed.world_size,
|
|
308
|
+
seed=seed,
|
|
309
|
+
interval=start_interval,
|
|
310
|
+
)
|
|
311
|
+
elif distributed.world_size > 1:
|
|
312
|
+
sampler = EvalSampler(dataset, rank=distributed.global_rank, world_size=distributed.world_size)
|
|
313
|
+
else:
|
|
314
|
+
sampler = None
|
|
315
|
+
|
|
316
|
+
base_loader = DataLoader(
|
|
317
|
+
dataset=dataset,
|
|
318
|
+
collate_fn=collate_fn,
|
|
319
|
+
sampler=sampler,
|
|
320
|
+
drop_last=is_training,
|
|
321
|
+
batch_size=batch_size,
|
|
322
|
+
num_workers=num_workers,
|
|
323
|
+
persistent_workers=persistent_workers and num_workers > 0,
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
loader = LoaderBundle(
|
|
327
|
+
loader=base_loader,
|
|
328
|
+
num_batches=len(base_loader),
|
|
329
|
+
num_samples=len(sampler) * distributed.world_size if is_training else len(dataset),
|
|
330
|
+
sampler=sampler,
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
return loader
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def _create_loader_hf_naflex(
|
|
337
|
+
dataset,
|
|
338
|
+
naflex_factory: Callable,
|
|
339
|
+
num_samples: Optional[int],
|
|
340
|
+
num_workers: int,
|
|
341
|
+
persistent_workers: bool,
|
|
342
|
+
start_interval: int,
|
|
343
|
+
seed: int,
|
|
344
|
+
distributed: DistributedCfg,
|
|
345
|
+
handler: Optional[Callable],
|
|
346
|
+
):
|
|
347
|
+
""" Train loader w/ schedule driven NaFlex batching over a map-style dataset. """
|
|
348
|
+
from chugr.naflex import NaFlexMapDataset
|
|
349
|
+
|
|
350
|
+
# samples per interval (all ranks), the schedule rounds up to full batches per rank / worker
|
|
351
|
+
num_samples = min(num_samples, len(dataset)) if num_samples else len(dataset)
|
|
352
|
+
scheduler, processors, image_key = naflex_factory(num_samples)
|
|
353
|
+
interval_count = SharedCount(start_interval)
|
|
354
|
+
# NOTE not wrapped in SafeDataset, the NaFlex wrapper replaces samples that fail to load or process
|
|
355
|
+
naflex_dataset = NaFlexMapDataset(
|
|
356
|
+
dataset,
|
|
357
|
+
scheduler=scheduler,
|
|
358
|
+
processors=processors,
|
|
359
|
+
image_key=image_key,
|
|
360
|
+
interval=interval_count,
|
|
361
|
+
rank=distributed.global_rank,
|
|
362
|
+
world_size=distributed.world_size,
|
|
363
|
+
seed=seed,
|
|
364
|
+
handler=handler,
|
|
365
|
+
)
|
|
366
|
+
# worker seeds (augmentation, replacement samples) differ across ranks, indices / schedule use the shared seed
|
|
367
|
+
dl_generator = torch.Generator()
|
|
368
|
+
dl_generator.manual_seed(rank_seed(seed, distributed.global_rank))
|
|
369
|
+
base_loader = DataLoader(
|
|
370
|
+
dataset=naflex_dataset,
|
|
371
|
+
batch_size=None, # batching done by the dataset wrapper
|
|
372
|
+
shuffle=False,
|
|
373
|
+
num_workers=num_workers,
|
|
374
|
+
persistent_workers=persistent_workers and num_workers > 0,
|
|
375
|
+
generator=dl_generator,
|
|
376
|
+
worker_init_fn=seed_worker,
|
|
377
|
+
)
|
|
378
|
+
num_workers_nonzero = max(num_workers, 1)
|
|
379
|
+
return LoaderBundle(
|
|
380
|
+
loader=base_loader,
|
|
381
|
+
num_batches=scheduler.num_batches_for_workers(num_workers_nonzero),
|
|
382
|
+
num_samples=scheduler.num_samples_for_workers(num_workers_nonzero),
|
|
383
|
+
shared_interval=interval_count,
|
|
384
|
+
mean_batch_size=scheduler.mean_batch_size_for_workers(num_workers_nonzero),
|
|
385
|
+
)
|
chugr/hfds/wrappers.py
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from itertools import islice
|
|
3
|
+
from typing import Callable, List, Optional
|
|
4
|
+
|
|
5
|
+
from torch.utils.data import Dataset, IterableDataset
|
|
6
|
+
|
|
7
|
+
from chugr.common import SharedCount
|
|
8
|
+
|
|
9
|
+
_logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
class SafeDataset(Dataset):
|
|
12
|
+
"""
|
|
13
|
+
This is a Dataset wrapped by a try/except in the __getitem__ in case
|
|
14
|
+
the hfds datasets used have errors/corrupt data. A sample that fails to load is replaced by
|
|
15
|
+
the next loadable one (wrapping around at the end of the dataset), w/ a warning.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, original_dataset, max_retry=10):
|
|
19
|
+
self.ds = original_dataset
|
|
20
|
+
self.max_retry = max_retry
|
|
21
|
+
|
|
22
|
+
def __len__(self):
|
|
23
|
+
return len(self.ds)
|
|
24
|
+
|
|
25
|
+
def __getitem__(self, idx):
|
|
26
|
+
err = None
|
|
27
|
+
num = len(self.ds)
|
|
28
|
+
for try_idx in range(min(self.max_retry, num)):
|
|
29
|
+
try_at = (idx + try_idx) % num # wrap, idx + try_idx can run past the end
|
|
30
|
+
try:
|
|
31
|
+
item = self.ds[try_at]
|
|
32
|
+
except Exception as e:
|
|
33
|
+
err = e
|
|
34
|
+
continue
|
|
35
|
+
if try_idx:
|
|
36
|
+
_logger.warning(f'Sample {idx} failed to load ({err!r}), replaced by sample {try_at}.')
|
|
37
|
+
return item
|
|
38
|
+
raise err if err is not None else IndexError(idx)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class WrappedIterableDataset(IterableDataset):
|
|
43
|
+
""" Wrap a streaming (iterable) HF dataset for interval based training loops.
|
|
44
|
+
|
|
45
|
+
* The interval count (shared w/ dataloader workers) sets the dataset epoch (shard / buffer shuffle seed), set
|
|
46
|
+
from the main process only (via `set_interval_count`). The HF epoch is a shared memory tensor updated w/ a
|
|
47
|
+
non-atomic in-place add, workers setting it concurrently race (an overshot / differing epoch per worker breaks
|
|
48
|
+
the worker shard partition, shards are skipped or read twice).
|
|
49
|
+
* `num_samples` w/ `repeat=True` makes every dataloader worker yield exactly `num_samples` samples per
|
|
50
|
+
interval, wrapping around its share of the stream if it runs out (a few samples repeat), so every rank sees
|
|
51
|
+
the same number of batches (as the wds loader).
|
|
52
|
+
* `stages` are applied to the sample stream (generator fn taking an iterable, e.g. NaFlexBatcher which then
|
|
53
|
+
yields batches following its schedule).
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
def __init__(
|
|
57
|
+
self,
|
|
58
|
+
original_dataset,
|
|
59
|
+
interval_count=None,
|
|
60
|
+
max_retry=10,
|
|
61
|
+
num_samples: Optional[int] = None,
|
|
62
|
+
repeat: bool = False,
|
|
63
|
+
stages: Optional[List[Callable]] = None,
|
|
64
|
+
):
|
|
65
|
+
self.ds = original_dataset
|
|
66
|
+
self.max_retry = max_retry
|
|
67
|
+
self.interval_count = interval_count
|
|
68
|
+
self.ds.set_epoch(self._get_interval_count())
|
|
69
|
+
self.num_samples = num_samples
|
|
70
|
+
self.repeat = repeat
|
|
71
|
+
self.stages = list(stages or [])
|
|
72
|
+
|
|
73
|
+
def _get_interval_count(self):
|
|
74
|
+
if isinstance(self.interval_count, SharedCount):
|
|
75
|
+
return self.interval_count.get_value()
|
|
76
|
+
return self.interval_count or 0
|
|
77
|
+
|
|
78
|
+
def set_interval_count(self, interval_count):
|
|
79
|
+
""" Main process only, workers (persistent or not) see the shared count and HF epoch. """
|
|
80
|
+
if isinstance(self.interval_count, SharedCount):
|
|
81
|
+
self.interval_count.set_value(interval_count)
|
|
82
|
+
else:
|
|
83
|
+
self.interval_count = interval_count
|
|
84
|
+
self.ds.set_epoch(interval_count)
|
|
85
|
+
|
|
86
|
+
def _samples(self):
|
|
87
|
+
# NOTE no set_epoch here, see the class docstring
|
|
88
|
+
if not self.repeat:
|
|
89
|
+
yield from self.ds
|
|
90
|
+
return
|
|
91
|
+
while True:
|
|
92
|
+
# NOTE wraps w/ the same epoch (shuffle seed), a per rank / worker reshuffle could break the node split
|
|
93
|
+
count = 0
|
|
94
|
+
for sample in self.ds:
|
|
95
|
+
count += 1
|
|
96
|
+
yield sample
|
|
97
|
+
if not count:
|
|
98
|
+
raise RuntimeError(
|
|
99
|
+
'No samples for this dataloader worker, a repeating streaming dataset needs at least one data '
|
|
100
|
+
'shard (file) per worker of every rank.')
|
|
101
|
+
|
|
102
|
+
def __iter__(self):
|
|
103
|
+
src = self._samples()
|
|
104
|
+
if self.num_samples:
|
|
105
|
+
src = islice(src, self.num_samples)
|
|
106
|
+
for stage in self.stages:
|
|
107
|
+
src = stage(src)
|
|
108
|
+
yield from src
|
chugr/image/__init__.py
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
from .transforms_factory import build_image_transforms, create_image_preprocessor
|
|
2
|
+
from .build_transforms_doc import build_transforms_doc_better, build_transforms_doc_basic, build_transforms_doc_nougat, \
|
|
3
|
+
build_transforms_doc_naflex, ResizeToSeqLen
|
|
4
|
+
from .build_transforms_image import build_transforms_image_timm, build_transforms_image_basic
|