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/common/config.py
ADDED
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
from dataclasses import dataclass, field, fields, replace
|
|
2
|
+
from typing import Any, Dict, List, Optional, Tuple, Union, Sequence, Callable
|
|
3
|
+
|
|
4
|
+
from .serialize import Serializable
|
|
5
|
+
|
|
6
|
+
from .types import ImageFeatureInfo, ShardSpec, SourceSpec
|
|
7
|
+
from .urls import expand_urls
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def image_mode_to_chs(fmt: str):
|
|
11
|
+
if fmt is None:
|
|
12
|
+
return None
|
|
13
|
+
assert fmt in ('L', 'RGB') # could support more...
|
|
14
|
+
return 1 if fmt == 'L' else 3
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class ImageInputCfg(Serializable):
|
|
19
|
+
size: Optional[Tuple[int, int]] = (512, 512)
|
|
20
|
+
mode: Optional[str] = 'L'
|
|
21
|
+
mean: Optional[Union[float, Tuple[float, ...]]] = 0.5
|
|
22
|
+
std: Optional[Union[float, Tuple[float, ...]]] = 0.5
|
|
23
|
+
interpolation: Optional[str] = 'bicubic'
|
|
24
|
+
fill_color: Optional[Union[int, Tuple[int, ...]]] = 255
|
|
25
|
+
crop_margin: Optional[bool] = False
|
|
26
|
+
align_long_axis: Optional[bool] = False
|
|
27
|
+
transform_type: Optional[str] = 'image_basic'
|
|
28
|
+
resize_mode: Optional[str] = 'shortest'
|
|
29
|
+
patch_size: Optional[int] = None # patch size for naflex (variable aspect / resolution) transforms
|
|
30
|
+
max_seq_len: Optional[int] = None # max patch sequence length for naflex transforms
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def image_chs(self):
|
|
34
|
+
return image_mode_to_chs(self.mode)
|
|
35
|
+
|
|
36
|
+
def __post_init__(self):
|
|
37
|
+
image_chs = self.image_chs
|
|
38
|
+
if image_chs is not None:
|
|
39
|
+
# ensue mean/std attributes match # image_chs
|
|
40
|
+
for attr_name in ('mean', 'std'):
|
|
41
|
+
attr = getattr(self, attr_name)
|
|
42
|
+
if attr is not None and not isinstance(attr, Sequence):
|
|
43
|
+
attr = (attr,)
|
|
44
|
+
if image_chs == 1 and len(attr) > image_chs:
|
|
45
|
+
attr = (sum(attr) / len(attr),)
|
|
46
|
+
if image_chs > 1 and len(attr) == 1:
|
|
47
|
+
attr = attr * image_chs
|
|
48
|
+
assert len(attr) == image_chs
|
|
49
|
+
setattr(self, attr_name, attr)
|
|
50
|
+
|
|
51
|
+
# ensure fill color matches image_chs
|
|
52
|
+
if self.fill_color is not None:
|
|
53
|
+
if not isinstance(self.fill_color, Sequence):
|
|
54
|
+
self.fill_color = (self.fill_color,)
|
|
55
|
+
if image_chs == 1 and len(self.fill_color) > image_chs:
|
|
56
|
+
self.fill_color = (int(sum(self.fill_color) / len(self.fill_color)),)
|
|
57
|
+
if image_chs > 1 and len(self.fill_color) == 1:
|
|
58
|
+
self.fill_color = self.fill_color * image_chs
|
|
59
|
+
|
|
60
|
+
@classmethod
|
|
61
|
+
def empty(cls):
|
|
62
|
+
return cls(**{f.name: None for f in fields(cls)})
|
|
63
|
+
|
|
64
|
+
def set_default(self, right, inplace=False):
|
|
65
|
+
# set left fields from right fields if left fields are not-initialized (None)
|
|
66
|
+
changes = {
|
|
67
|
+
f.name: v for f in fields(self)
|
|
68
|
+
if (v := getattr(right, f.name)) is not None and getattr(self, f.name) is None
|
|
69
|
+
}
|
|
70
|
+
if inplace:
|
|
71
|
+
for k, v in changes.items():
|
|
72
|
+
setattr(self, k, v)
|
|
73
|
+
return self
|
|
74
|
+
else:
|
|
75
|
+
return replace(self, **changes)
|
|
76
|
+
|
|
77
|
+
def merge(self, right, inplace=False):
|
|
78
|
+
# merge from right to left for right fields that are not None
|
|
79
|
+
changes = {f.name: v for f in fields(self) if (v := getattr(right, f.name)) is not None}
|
|
80
|
+
if inplace:
|
|
81
|
+
for k, v in changes.items():
|
|
82
|
+
setattr(self, k, v)
|
|
83
|
+
return self
|
|
84
|
+
else:
|
|
85
|
+
return replace(self, **changes)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@dataclass
|
|
89
|
+
class ImageAugCfg(Serializable):
|
|
90
|
+
"""
|
|
91
|
+
A simple flat config struct for overriding common augmentation defaults.
|
|
92
|
+
|
|
93
|
+
Each image transform type supports different augmentations and have their own defaults,
|
|
94
|
+
this struct is intended to override defaults for common values, not necessarily to
|
|
95
|
+
cover all cases and define all augmentation possibilities across all schemes.
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
# resize scale bounds (1.0 = middle point = same scale)
|
|
99
|
+
scale: Optional[Tuple[float, float]] = None
|
|
100
|
+
|
|
101
|
+
# resize aspect ratio bounds (1.0 = 1:1)
|
|
102
|
+
ratio: Optional[Tuple[float, float]] = None
|
|
103
|
+
|
|
104
|
+
# color jitter, per item probs
|
|
105
|
+
color_jitter: Optional[Union[float, Tuple[float, float, float], Tuple[float, float, float, float]]] = None
|
|
106
|
+
|
|
107
|
+
# for simclr, control prob for applying any of the jitter probs above
|
|
108
|
+
color_jitter_prob: Optional[float] = None
|
|
109
|
+
|
|
110
|
+
# for preprocess w/ grayscale (simclr), control prob of converting to graysacle
|
|
111
|
+
grayscale_prob: Optional[float] = None
|
|
112
|
+
|
|
113
|
+
gaussian_blur_prob: Optional[float] = None
|
|
114
|
+
gaussian_blur_kernel_size: Optional[int] = None
|
|
115
|
+
|
|
116
|
+
# probability of applying random-erasing (timm style aug)
|
|
117
|
+
re_prob: Optional[float] = None
|
|
118
|
+
|
|
119
|
+
# number of random-erasing blocks (timm style aug)
|
|
120
|
+
re_count: Optional[int] = None
|
|
121
|
+
|
|
122
|
+
@classmethod
|
|
123
|
+
def clip(cls, **kwargs):
|
|
124
|
+
aug = cls(
|
|
125
|
+
scale=(0.9, 1.0),
|
|
126
|
+
ratio=(0.75, 1. / 0.75),
|
|
127
|
+
)
|
|
128
|
+
aug = replace(aug, **kwargs)
|
|
129
|
+
return aug
|
|
130
|
+
|
|
131
|
+
@classmethod
|
|
132
|
+
def imagenet(cls, **kwargs):
|
|
133
|
+
aug = cls(
|
|
134
|
+
scale=(0.08, 1.0),
|
|
135
|
+
ratio=(0.75, 1. / 0.75),
|
|
136
|
+
color_jitter=(0.4, 0.4, 0.4),
|
|
137
|
+
)
|
|
138
|
+
aug = replace(aug, **kwargs)
|
|
139
|
+
return aug
|
|
140
|
+
|
|
141
|
+
@classmethod
|
|
142
|
+
def simclr(cls, **kwargs):
|
|
143
|
+
aug = cls(
|
|
144
|
+
scale=(0.08, 1.0),
|
|
145
|
+
ratio=(0.75, 1. / 0.75),
|
|
146
|
+
color_jitter=(0.4, 0.4, 0.4, 0.1),
|
|
147
|
+
color_jitter_prob=0.8,
|
|
148
|
+
grayscale_prob=0.2,
|
|
149
|
+
gaussian_blur_prob=0.5,
|
|
150
|
+
#gaussian_blur_kernel_size=23,
|
|
151
|
+
)
|
|
152
|
+
aug = replace(aug, **kwargs)
|
|
153
|
+
return aug
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
# Vision preprocessing config
|
|
157
|
+
@dataclass
|
|
158
|
+
class PreprocessCfg(Serializable):
|
|
159
|
+
image_input: ImageInputCfg = field(default_factory=ImageInputCfg)
|
|
160
|
+
aug_cfg: Optional[ImageAugCfg] = None
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@dataclass
|
|
164
|
+
class DataArg(Serializable):
|
|
165
|
+
""" Data source argument in an argument friendly form (multiple sources represented in string)
|
|
166
|
+
"""
|
|
167
|
+
source: str
|
|
168
|
+
split: Optional[str] = None
|
|
169
|
+
sampling_weight: Optional[str] = None
|
|
170
|
+
template: Optional[str] = None # template to transform url for use
|
|
171
|
+
num_samples: Optional[Union[int, str]] = None
|
|
172
|
+
data_dir: Optional[str] = None
|
|
173
|
+
|
|
174
|
+
batch_size: int = 1
|
|
175
|
+
format: str = "wds" # e.g. "hfds", "hfids", or "wds"
|
|
176
|
+
|
|
177
|
+
resampled: bool = False # sample shards with replacement
|
|
178
|
+
multi_interval: bool = True
|
|
179
|
+
persistent_workers: bool = True
|
|
180
|
+
num_workers: int = 4
|
|
181
|
+
|
|
182
|
+
# NaFlex schedule driven batching (variable resolution buckets w/ token budget batch sizes), enabled when
|
|
183
|
+
# naflex_seq_lens is set (batch_size is then ignored for training).
|
|
184
|
+
naflex_seq_lens: Optional[List[int]] = None
|
|
185
|
+
naflex_seq_len_probs: Optional[List[float]] = None
|
|
186
|
+
naflex_max_tokens: Optional[int] = None # tokens (patches + text) per batch per rank
|
|
187
|
+
naflex_text_tokens: int = 0 # per row text token cost added when sizing batches
|
|
188
|
+
naflex_batch_divisor: int = 8
|
|
189
|
+
length_bucketing: bool = False # pool sort samples by annotation text length
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def split_sources(
|
|
193
|
+
source: str,
|
|
194
|
+
split: Optional[str] = None,
|
|
195
|
+
sampling_weights: Optional[Union[str, List[float]]] = None,
|
|
196
|
+
num_samples: Optional[Union[int, str, List[int]]] = None,
|
|
197
|
+
):
|
|
198
|
+
if '::' in source:
|
|
199
|
+
source_split = source.split('::')
|
|
200
|
+
else:
|
|
201
|
+
source_split = [source]
|
|
202
|
+
num_sources = len(source_split)
|
|
203
|
+
|
|
204
|
+
if sampling_weights is not None:
|
|
205
|
+
if isinstance(sampling_weights, str):
|
|
206
|
+
weights_split = sampling_weights.split('::')
|
|
207
|
+
sampling_weights = [float(w) for w in weights_split]
|
|
208
|
+
assert len(sampling_weights) == num_sources
|
|
209
|
+
|
|
210
|
+
num_samples_per_source = None
|
|
211
|
+
if num_samples is not None:
|
|
212
|
+
if isinstance(num_samples, str):
|
|
213
|
+
num_samples_split = num_samples.split('::')
|
|
214
|
+
num_samples = [int(s) for s in num_samples_split]
|
|
215
|
+
|
|
216
|
+
try:
|
|
217
|
+
len(num_samples)
|
|
218
|
+
except Exception:
|
|
219
|
+
num_samples_per_source = [None] * num_sources
|
|
220
|
+
else:
|
|
221
|
+
num_samples_per_source = num_samples
|
|
222
|
+
num_samples = sum(num_samples_per_source)
|
|
223
|
+
finally:
|
|
224
|
+
assert len(num_samples_per_source) == num_sources
|
|
225
|
+
|
|
226
|
+
output = []
|
|
227
|
+
for i, s in enumerate(source_split):
|
|
228
|
+
output.append(SourceSpec(
|
|
229
|
+
url=s,
|
|
230
|
+
split=split,
|
|
231
|
+
sampling_weight=None if sampling_weights is None else sampling_weights[i],
|
|
232
|
+
num_samples=None if num_samples_per_source is None else num_samples_per_source[i],
|
|
233
|
+
))
|
|
234
|
+
|
|
235
|
+
return output, num_samples
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
# FIXME add code to resolve shard information from _info.yaml or .json files (see dataset_info.py)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def source_to_shard_spec(
|
|
242
|
+
source: Union[str, SourceSpec, List[SourceSpec]],
|
|
243
|
+
):
|
|
244
|
+
if isinstance(source, str):
|
|
245
|
+
source_list = [SourceSpec(url=source)]
|
|
246
|
+
elif isinstance(source, SourceSpec):
|
|
247
|
+
source_list = [source]
|
|
248
|
+
else:
|
|
249
|
+
assert isinstance(source[0], SourceSpec)
|
|
250
|
+
source_list = source
|
|
251
|
+
|
|
252
|
+
# process weights first in case some are set and some are not
|
|
253
|
+
if not all(s.sampling_weight is None for s in source_list):
|
|
254
|
+
weights = [s.sampling_weight if s.sampling_weight else 1.0 for s in source_list]
|
|
255
|
+
else:
|
|
256
|
+
weights = [None] * len(source_list)
|
|
257
|
+
|
|
258
|
+
all_urls = []
|
|
259
|
+
all_weights = []
|
|
260
|
+
for s, w in zip(source_list, weights):
|
|
261
|
+
expanded_urls, expanded_weights = expand_urls(s.url, weights=w)
|
|
262
|
+
all_urls.extend(expanded_urls)
|
|
263
|
+
if expanded_weights:
|
|
264
|
+
all_weights.extend(expanded_weights)
|
|
265
|
+
all_weights = all_weights or None
|
|
266
|
+
sizes = None # FIXME resolve sizes
|
|
267
|
+
|
|
268
|
+
ss = ShardSpec(urls=all_urls, weights=all_weights, sizes=sizes)
|
|
269
|
+
return ss
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
@dataclass
|
|
273
|
+
class DataCfg(Serializable):
|
|
274
|
+
source: Union[str, SourceSpec, List[SourceSpec]]
|
|
275
|
+
split: Optional[str] = None
|
|
276
|
+
num_samples: Optional[int] = None # overrides num_samples across sources if set
|
|
277
|
+
data_dir: Optional[str] = None
|
|
278
|
+
|
|
279
|
+
batch_size: Optional[int] = 1
|
|
280
|
+
format: str = "wds" # e.g. "hfds", "hfids", or "wds".
|
|
281
|
+
|
|
282
|
+
resampled: bool = False # sample shards with replacement
|
|
283
|
+
multi_interval: bool = True
|
|
284
|
+
persistent_workers: bool = True
|
|
285
|
+
num_workers: int = 4
|
|
286
|
+
|
|
287
|
+
# NaFlex schedule driven batching (variable resolution buckets w/ token budget batch sizes), enabled when
|
|
288
|
+
# naflex_seq_lens is set (batch_size is then ignored for training).
|
|
289
|
+
naflex_seq_lens: Optional[List[int]] = None
|
|
290
|
+
naflex_seq_len_probs: Optional[List[float]] = None
|
|
291
|
+
naflex_max_tokens: Optional[int] = None # tokens (patches + text) per batch per rank
|
|
292
|
+
naflex_text_tokens: int = 0 # per row text token cost added when sizing batches
|
|
293
|
+
naflex_batch_divisor: int = 8
|
|
294
|
+
length_bucketing: bool = False # pool sort samples by annotation text length
|
|
295
|
+
|
|
296
|
+
@classmethod
|
|
297
|
+
def from_arg(cls, data_arg: DataArg):
|
|
298
|
+
sources, num_samples = split_sources(
|
|
299
|
+
data_arg.source,
|
|
300
|
+
split=data_arg.split,
|
|
301
|
+
sampling_weights=data_arg.sampling_weight,
|
|
302
|
+
num_samples=data_arg.num_samples,
|
|
303
|
+
)
|
|
304
|
+
return cls(
|
|
305
|
+
source=sources if len(sources) > 1 else sources[0],
|
|
306
|
+
split=data_arg.split,
|
|
307
|
+
num_samples=num_samples,
|
|
308
|
+
data_dir=data_arg.data_dir,
|
|
309
|
+
batch_size=data_arg.batch_size,
|
|
310
|
+
format=data_arg.format,
|
|
311
|
+
resampled=data_arg.resampled,
|
|
312
|
+
multi_interval=data_arg.multi_interval,
|
|
313
|
+
persistent_workers=data_arg.persistent_workers,
|
|
314
|
+
num_workers=data_arg.num_workers,
|
|
315
|
+
naflex_seq_lens=data_arg.naflex_seq_lens,
|
|
316
|
+
naflex_seq_len_probs=data_arg.naflex_seq_len_probs,
|
|
317
|
+
naflex_max_tokens=data_arg.naflex_max_tokens,
|
|
318
|
+
naflex_text_tokens=data_arg.naflex_text_tokens,
|
|
319
|
+
naflex_batch_divisor=data_arg.naflex_batch_divisor,
|
|
320
|
+
length_bucketing=data_arg.length_bucketing,
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
@property
|
|
324
|
+
def shard_spec(self):
|
|
325
|
+
return source_to_shard_spec(self.source)
|
|
326
|
+
|
|
327
|
+
def __post_init__(self):
|
|
328
|
+
if self.num_workers == 0:
|
|
329
|
+
self.persistent_workers = False
|
|
330
|
+
|
|
331
|
+
@dataclass
|
|
332
|
+
class DistributedCfg:
|
|
333
|
+
world_size: int = 1
|
|
334
|
+
local_rank: int = 0
|
|
335
|
+
global_rank: int = 0
|
chugr/common/random.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import random
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
from .types import SharedCount
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
_RANK_SEED_STRIDE = 1000003
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def rank_seed(seed: int, rank: int = 0) -> int:
|
|
12
|
+
""" Seed for per rank randomness (process RNGs, dataloader worker seeds).
|
|
13
|
+
|
|
14
|
+
Ranks are spaced by a large stride (vs seed + rank) so runs w/ nearby seeds don't share random streams
|
|
15
|
+
(seed 42 rank 1 == seed 43 rank 0), rank 0 keeps `seed` so single process runs are unchanged.
|
|
16
|
+
"""
|
|
17
|
+
return seed + _RANK_SEED_STRIDE * rank
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def seed_worker(worker_id):
|
|
21
|
+
import torch
|
|
22
|
+
worker_seed = torch.initial_seed()
|
|
23
|
+
random.seed(worker_seed)
|
|
24
|
+
np.random.seed(worker_seed % 2**32)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def get_pytorch_worker_seed(increment=0, initial_seed=None):
|
|
28
|
+
"""get dataloader worker seed from pytorch
|
|
29
|
+
"""
|
|
30
|
+
from torch.utils.data import get_worker_info
|
|
31
|
+
|
|
32
|
+
increment_value = increment.get_value() if isinstance(increment, SharedCount) else increment
|
|
33
|
+
worker_info = get_worker_info()
|
|
34
|
+
if worker_info is not None:
|
|
35
|
+
# favour using the seed already created for pytorch dataloader workers if it exists
|
|
36
|
+
seed = worker_info.seed
|
|
37
|
+
num_workers = worker_info.num_workers
|
|
38
|
+
if increment_value:
|
|
39
|
+
# space out seed increments so they can't overlap across workers in different iterations
|
|
40
|
+
seed += increment_value * max(1, num_workers)
|
|
41
|
+
else:
|
|
42
|
+
# a fallback when no dataloader workers are present (num_workers=0)
|
|
43
|
+
import torch
|
|
44
|
+
|
|
45
|
+
if initial_seed is None:
|
|
46
|
+
initial_seed = torch.initial_seed()
|
|
47
|
+
|
|
48
|
+
# generate seed from initial via torch.Generator so it matches DL worker seeds
|
|
49
|
+
seed = torch.empty((), dtype=torch.int64).random_(
|
|
50
|
+
generator=torch.Generator().manual_seed(initial_seed)).item()
|
|
51
|
+
|
|
52
|
+
if increment_value:
|
|
53
|
+
seed += increment_value
|
|
54
|
+
|
|
55
|
+
return seed
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def shuffled_interval_indices(dataset_len: int, num_indices: int, interval: int = 0, seed: int = 0):
|
|
59
|
+
""" Shuffled dataset indices for one interval of a map-style dataset (all ranks, split w/ [rank::world_size]).
|
|
60
|
+
|
|
61
|
+
Each pass over the dataset is a fresh permutation (seed + pass), intervals consume the permutations
|
|
62
|
+
sequentially (interval i starts where i - 1 ended), so intervals shorter than the dataset still cycle through
|
|
63
|
+
every sample before repeating. Identical across ranks / workers for the same args.
|
|
64
|
+
"""
|
|
65
|
+
import torch
|
|
66
|
+
assert dataset_len > 0, 'dataset is empty'
|
|
67
|
+
epoch, offset = divmod(interval * num_indices, dataset_len)
|
|
68
|
+
indices = []
|
|
69
|
+
while len(indices) < num_indices:
|
|
70
|
+
g = torch.Generator()
|
|
71
|
+
g.manual_seed(seed + epoch)
|
|
72
|
+
perm = torch.randperm(dataset_len, generator=g).tolist()
|
|
73
|
+
indices.extend(perm[offset:])
|
|
74
|
+
offset = 0
|
|
75
|
+
epoch += 1
|
|
76
|
+
return indices[:num_indices]
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
""" Minimal dataclass (de)serialization
|
|
2
|
+
|
|
3
|
+
A small replacement for the subset of `simple_parsing.helpers.Serializable` used by chugr & pixparse.
|
|
4
|
+
Supports nested dataclasses, Optional / Union / Tuple / List / Dict fields, and json or yaml files.
|
|
5
|
+
"""
|
|
6
|
+
import dataclasses
|
|
7
|
+
import json
|
|
8
|
+
import types
|
|
9
|
+
import typing
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any, Dict, Type, TypeVar, Union
|
|
12
|
+
|
|
13
|
+
T = TypeVar('T')
|
|
14
|
+
|
|
15
|
+
_NONE_TYPE = type(None)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _is_union(tp) -> bool:
|
|
19
|
+
origin = typing.get_origin(tp)
|
|
20
|
+
return origin is Union or origin is types.UnionType
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _unwrap_optional(tp):
|
|
24
|
+
""" Optional[X] -> X, leave other unions untouched """
|
|
25
|
+
if _is_union(tp):
|
|
26
|
+
args = [a for a in typing.get_args(tp) if a is not _NONE_TYPE]
|
|
27
|
+
if len(args) == 1:
|
|
28
|
+
return args[0]
|
|
29
|
+
return tp
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def resolve_field_types(cls) -> Dict[str, Any]:
|
|
33
|
+
try:
|
|
34
|
+
return typing.get_type_hints(cls)
|
|
35
|
+
except Exception:
|
|
36
|
+
return {f.name: f.type for f in dataclasses.fields(cls)}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def to_dict(obj) -> Any:
|
|
40
|
+
""" Recursively convert dataclasses (and containers of) to plain python types. """
|
|
41
|
+
if dataclasses.is_dataclass(obj) and not isinstance(obj, type):
|
|
42
|
+
out = {}
|
|
43
|
+
for f in dataclasses.fields(obj):
|
|
44
|
+
if not f.init and not f.metadata.get('serialize', False):
|
|
45
|
+
continue
|
|
46
|
+
out[f.name] = to_dict(getattr(obj, f.name))
|
|
47
|
+
return out
|
|
48
|
+
if isinstance(obj, (list, tuple)):
|
|
49
|
+
return [to_dict(v) for v in obj]
|
|
50
|
+
if isinstance(obj, dict):
|
|
51
|
+
return {k: to_dict(v) for k, v in obj.items()}
|
|
52
|
+
if isinstance(obj, Path):
|
|
53
|
+
return str(obj)
|
|
54
|
+
return obj
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def from_value(tp, value):
|
|
58
|
+
""" Convert a plain python value to the annotated type `tp`, recursing into dataclasses. """
|
|
59
|
+
if value is None:
|
|
60
|
+
return None
|
|
61
|
+
tp = _unwrap_optional(tp)
|
|
62
|
+
|
|
63
|
+
if dataclasses.is_dataclass(tp):
|
|
64
|
+
if isinstance(value, tp):
|
|
65
|
+
return value
|
|
66
|
+
assert isinstance(value, dict), f'Expected dict for {tp}, got {type(value)}'
|
|
67
|
+
return from_dict(tp, value)
|
|
68
|
+
|
|
69
|
+
if _is_union(tp):
|
|
70
|
+
# try dataclass members first when given a dict, otherwise pass through unchanged
|
|
71
|
+
if isinstance(value, dict):
|
|
72
|
+
for a in typing.get_args(tp):
|
|
73
|
+
if dataclasses.is_dataclass(a):
|
|
74
|
+
try:
|
|
75
|
+
return from_dict(a, value)
|
|
76
|
+
except (TypeError, AssertionError):
|
|
77
|
+
continue
|
|
78
|
+
if isinstance(value, list):
|
|
79
|
+
for a in typing.get_args(tp):
|
|
80
|
+
if typing.get_origin(a) in (list, tuple):
|
|
81
|
+
return from_value(a, value)
|
|
82
|
+
return value
|
|
83
|
+
|
|
84
|
+
origin = typing.get_origin(tp)
|
|
85
|
+
args = typing.get_args(tp)
|
|
86
|
+
if origin is tuple:
|
|
87
|
+
if not isinstance(value, (list, tuple)):
|
|
88
|
+
value = (value,)
|
|
89
|
+
if len(args) == 2 and args[1] is Ellipsis:
|
|
90
|
+
return tuple(from_value(args[0], v) for v in value)
|
|
91
|
+
if args and len(args) == len(value):
|
|
92
|
+
return tuple(from_value(a, v) for a, v in zip(args, value))
|
|
93
|
+
return tuple(value)
|
|
94
|
+
if origin is list:
|
|
95
|
+
item_tp = args[0] if args else Any
|
|
96
|
+
return [from_value(item_tp, v) for v in value]
|
|
97
|
+
if origin is dict:
|
|
98
|
+
val_tp = args[1] if len(args) == 2 else Any
|
|
99
|
+
return {k: from_value(val_tp, v) for k, v in value.items()}
|
|
100
|
+
if tp is float and isinstance(value, int) and not isinstance(value, bool):
|
|
101
|
+
return float(value)
|
|
102
|
+
return value
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def from_dict(cls: Type[T], data: Dict[str, Any]) -> T:
|
|
106
|
+
field_types = resolve_field_types(cls)
|
|
107
|
+
init_fields = {f.name for f in dataclasses.fields(cls) if f.init}
|
|
108
|
+
unknown = set(data.keys()) - init_fields
|
|
109
|
+
if unknown:
|
|
110
|
+
raise TypeError(f'Unknown fields {sorted(unknown)} for {cls.__name__}.')
|
|
111
|
+
kwargs = {k: from_value(field_types[k], v) for k, v in data.items()}
|
|
112
|
+
return cls(**kwargs)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _load_file(path: Union[str, Path]) -> Dict[str, Any]:
|
|
116
|
+
path = Path(path)
|
|
117
|
+
text = path.read_text()
|
|
118
|
+
if path.suffix in ('.yaml', '.yml'):
|
|
119
|
+
import yaml
|
|
120
|
+
return yaml.safe_load(text) or {}
|
|
121
|
+
return json.loads(text)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class Serializable:
|
|
125
|
+
""" Mixin for dataclasses w/ dict, json, and yaml (de)serialization. """
|
|
126
|
+
|
|
127
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
128
|
+
return to_dict(self)
|
|
129
|
+
|
|
130
|
+
@classmethod
|
|
131
|
+
def from_dict(cls: Type[T], data: Dict[str, Any]) -> T:
|
|
132
|
+
return from_dict(cls, data)
|
|
133
|
+
|
|
134
|
+
@classmethod
|
|
135
|
+
def load(cls: Type[T], path: Union[str, Path]) -> T:
|
|
136
|
+
return from_dict(cls, _load_file(path))
|
|
137
|
+
|
|
138
|
+
def save(self, path: Union[str, Path]):
|
|
139
|
+
path = Path(path)
|
|
140
|
+
if path.suffix in ('.yaml', '.yml'):
|
|
141
|
+
path.write_text(self.dumps_yaml())
|
|
142
|
+
else:
|
|
143
|
+
path.write_text(self.dumps_json())
|
|
144
|
+
|
|
145
|
+
def dumps_json(self, indent: int = 2) -> str:
|
|
146
|
+
return json.dumps(self.to_dict(), indent=indent)
|
|
147
|
+
|
|
148
|
+
def dumps_yaml(self) -> str:
|
|
149
|
+
import yaml
|
|
150
|
+
return yaml.safe_dump(self.to_dict(), sort_keys=False)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@dataclass
|
|
6
|
+
class DataTaskCfg:
|
|
7
|
+
"""
|
|
8
|
+
Attributes:
|
|
9
|
+
...
|
|
10
|
+
output_tuple: Output tuples instead of dicts from task pipeline.
|
|
11
|
+
filter_valid: Filter out invalid / incomplete samples.
|
|
12
|
+
flatten_json: Flatten json dicts into parent sample dict. Common to have in wds datasets.
|
|
13
|
+
error_handler: 'specifies which error (exception) handler should be used, 'ignore_and_continue'
|
|
14
|
+
is good for training, 'reraise_exception' for debugging purposes.
|
|
15
|
+
"""
|
|
16
|
+
decode_fn: Optional[Callable] = None
|
|
17
|
+
image_process_fn: Optional[Callable] = None
|
|
18
|
+
image_process_factory: Optional[Callable] = None # seq_len -> image process fn (NaFlex bucketed batching)
|
|
19
|
+
text_process_fn: Optional[Callable] = None
|
|
20
|
+
decode_and_process_fn: Optional[Callable] = None
|
|
21
|
+
output_tuple: bool = False # output features as tuple instead of dictionary
|
|
22
|
+
filter_valid: bool = False # enable filter to keep samples with valid key-values
|
|
23
|
+
flatten_json: bool = True # flatten nested 'json' dicts into parent sample
|
|
24
|
+
error_handler: str = 'reraise_exception'
|