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
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from functools import partial
|
|
3
|
+
from typing import Callable, Optional, Union
|
|
4
|
+
|
|
5
|
+
import webdataset as wds
|
|
6
|
+
|
|
7
|
+
from chugr.common import DataTaskCfg, ImageFeatureInfo, FeatureInfo
|
|
8
|
+
from chugr.wds import get_error_handler, create_image_decoder
|
|
9
|
+
|
|
10
|
+
_DEFAULT_IMG_KEY = "jpg;png;jpeg;webp;tif"
|
|
11
|
+
_DEFAULT_TXT_KEY = "txt"
|
|
12
|
+
_DEFAULT_IMG_KEY_TUPLE = tuple(_DEFAULT_IMG_KEY.split(';'))
|
|
13
|
+
_DEFAULT_TXT_KEY_TUPLE = tuple(_DEFAULT_TXT_KEY.split(';'))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class DataTaskImageTextCfg(DataTaskCfg):
|
|
18
|
+
image_input_feat: ImageFeatureInfo = ImageFeatureInfo()
|
|
19
|
+
text_input_feat: FeatureInfo = FeatureInfo('text', input_key=_DEFAULT_TXT_KEY)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def filter_incomplete(
|
|
23
|
+
sample,
|
|
24
|
+
image_key=_DEFAULT_IMG_KEY_TUPLE,
|
|
25
|
+
text_key=_DEFAULT_TXT_KEY_TUPLE
|
|
26
|
+
):
|
|
27
|
+
has_caption = any(k in sample for k in text_key)
|
|
28
|
+
has_image = any(k in sample for k in image_key)
|
|
29
|
+
return has_caption and has_image
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def build_task_pipeline_image_text(
|
|
33
|
+
cfg: DataTaskImageTextCfg,
|
|
34
|
+
):
|
|
35
|
+
""" Create pipeline for dual image & text input pipelines.
|
|
36
|
+
"""
|
|
37
|
+
handler = get_error_handler(cfg.error_handler)
|
|
38
|
+
pipe = []
|
|
39
|
+
|
|
40
|
+
# FIXME add support for caption target for caption tasks or use a separate pipe?
|
|
41
|
+
|
|
42
|
+
if cfg.filter_valid:
|
|
43
|
+
filter_fn = partial(
|
|
44
|
+
filter_incomplete,
|
|
45
|
+
image_key=tuple(cfg.image_input_feat.input_key.split(';')),
|
|
46
|
+
text_key=tuple(cfg.text_input_feat.input_key.split(';')),
|
|
47
|
+
)
|
|
48
|
+
pipe += [
|
|
49
|
+
wds.select(filter_fn)
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
if cfg.decode_and_process_fn:
|
|
53
|
+
pipe += [
|
|
54
|
+
wds.map(cfg.decode_and_process_fn)
|
|
55
|
+
]
|
|
56
|
+
else:
|
|
57
|
+
decode_fn = create_image_decoder(
|
|
58
|
+
cfg.decode_fn,
|
|
59
|
+
image_mode=cfg.image_input_feat.image_mode,
|
|
60
|
+
handler=handler,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
rename_dict = {
|
|
64
|
+
cfg.image_input_feat.output_name: cfg.image_input_feat.input_key,
|
|
65
|
+
cfg.text_input_feat.output_name: cfg.text_input_feat.input_key,
|
|
66
|
+
}
|
|
67
|
+
pipe += [
|
|
68
|
+
decode_fn,
|
|
69
|
+
wds.rename(**rename_dict, keep=False, handler=handler),
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
map_dict = {}
|
|
73
|
+
if cfg.image_process_fn is not None:
|
|
74
|
+
map_dict[cfg.image_input_feat.output_name] = cfg.image_process_fn
|
|
75
|
+
if cfg.text_process_fn is not None:
|
|
76
|
+
map_dict[cfg.text_input_feat.output_name] = cfg.text_process_fn
|
|
77
|
+
if map_dict:
|
|
78
|
+
pipe += [
|
|
79
|
+
wds.map_dict(**map_dict, handler=handler)
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
if cfg.output_tuple:
|
|
83
|
+
pipe += [
|
|
84
|
+
wds.to_tuple(
|
|
85
|
+
cfg.image_input_feat.output_name,
|
|
86
|
+
cfg.text_input_feat.output_name,
|
|
87
|
+
)
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
return pipe
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Callable
|
|
3
|
+
|
|
4
|
+
import webdataset as wds
|
|
5
|
+
|
|
6
|
+
from chugr.common import DataTaskCfg, FeatureInfo, ImageFeatureInfo
|
|
7
|
+
from chugr.doc import DocReadProcessor
|
|
8
|
+
from chugr.wds.helpers import log_and_continue
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class DataTaskManualCfg(DataTaskCfg):
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def build_task_pipeline_manual(
|
|
17
|
+
cfg: DataTaskManualCfg,
|
|
18
|
+
):
|
|
19
|
+
assert cfg.decode_and_process_fn is not None
|
|
20
|
+
# a pipeline that relies fully on passed in decode_and_process_fn, other cfg fields ignored
|
|
21
|
+
pipe = [
|
|
22
|
+
wds.map(
|
|
23
|
+
cfg.decode_and_process_fn,
|
|
24
|
+
handler=log_and_continue,
|
|
25
|
+
)
|
|
26
|
+
]
|
|
27
|
+
return pipe
|
chugr/text/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .tokenization import tokenize, text_input_to_target, prepare_text_input, create_text_preprocessor
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from functools import partial
|
|
3
|
+
from typing import Callable, List, Optional, Union
|
|
4
|
+
|
|
5
|
+
import torch
|
|
6
|
+
|
|
7
|
+
_logger = logging.getLogger(__name__)
|
|
8
|
+
_num_prompts_truncated = 0
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def prompt_end_pos(tokens: torch.Tensor, prompt_end_token_id: int, empty_default: int = 0) -> int:
|
|
12
|
+
end_pos = torch.nonzero(tokens == prompt_end_token_id)
|
|
13
|
+
return end_pos[-1].item() if end_pos.numel() > 0 else empty_default
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def text_input_to_target(
|
|
17
|
+
text_input_ids: torch.Tensor,
|
|
18
|
+
tokenizer: Optional[Callable] = None,
|
|
19
|
+
prompt_end_token: Optional[Union[str, int]] = None,
|
|
20
|
+
ignore_id: int = -100,
|
|
21
|
+
pad_token_id: Optional[int] = None,
|
|
22
|
+
):
|
|
23
|
+
target = text_input_ids.clone()
|
|
24
|
+
|
|
25
|
+
if pad_token_id is None:
|
|
26
|
+
assert tokenizer is not None, 'tokenizer must be specified if pad_token_id is not.'
|
|
27
|
+
pad_token_id = tokenizer.pad_token_id
|
|
28
|
+
|
|
29
|
+
# model doesn't need to predict pad token
|
|
30
|
+
target[target == pad_token_id] = ignore_id
|
|
31
|
+
|
|
32
|
+
if prompt_end_token is not None:
|
|
33
|
+
if isinstance(prompt_end_token, str):
|
|
34
|
+
assert tokenizer is not None, 'tokenizer must be specified if prompt_end_token_id required.'
|
|
35
|
+
prompt_end_token_id = tokenizer.convert_tokens_to_ids(prompt_end_token)
|
|
36
|
+
else:
|
|
37
|
+
prompt_end_token_id = prompt_end_token # already an int
|
|
38
|
+
|
|
39
|
+
# model doesn't need to predict prompt (for VQA)
|
|
40
|
+
end_pos = prompt_end_pos(target, prompt_end_token_id, empty_default=-1)
|
|
41
|
+
if end_pos < 0:
|
|
42
|
+
# prompt end truncated away (prompt longer than max_length), nothing left to predict
|
|
43
|
+
target[:] = ignore_id
|
|
44
|
+
else:
|
|
45
|
+
target[:end_pos + 1] = ignore_id
|
|
46
|
+
|
|
47
|
+
return target
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def tokenize(
|
|
51
|
+
text: str,
|
|
52
|
+
tokenizer: Callable,
|
|
53
|
+
max_length: int,
|
|
54
|
+
ids_only: bool = True,
|
|
55
|
+
):
|
|
56
|
+
output = tokenizer(
|
|
57
|
+
text,
|
|
58
|
+
add_special_tokens=False,
|
|
59
|
+
return_tensors="pt",
|
|
60
|
+
max_length=max_length,
|
|
61
|
+
padding="max_length",
|
|
62
|
+
truncation=True,
|
|
63
|
+
)
|
|
64
|
+
if ids_only:
|
|
65
|
+
return output.input_ids[0]
|
|
66
|
+
return output
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def truncate_prompt(
|
|
70
|
+
token_ids: List[int],
|
|
71
|
+
max_length: int,
|
|
72
|
+
prompt_end_token_id: int,
|
|
73
|
+
special_token_ids=(),
|
|
74
|
+
) -> List[int]:
|
|
75
|
+
""" Shorten the prompt of an over-long `prompt + target` token sequence so the prompt end token and the target
|
|
76
|
+
still fit in `max_length`.
|
|
77
|
+
|
|
78
|
+
Tokens are removed from the end of the prompt body, the prompt's leading special tokens (task start, question
|
|
79
|
+
prefix) and trailing special tokens (question suffix, prompt end) are kept. Returns the ids unchanged if they
|
|
80
|
+
fit or there is no prompt end token, and may still return more than `max_length` tokens if the target alone
|
|
81
|
+
is too long (the caller truncates the tail).
|
|
82
|
+
"""
|
|
83
|
+
over = len(token_ids) - max_length
|
|
84
|
+
ends = [i for i, t in enumerate(token_ids) if t == prompt_end_token_id]
|
|
85
|
+
if over <= 0 or not ends:
|
|
86
|
+
return token_ids
|
|
87
|
+
special = set(special_token_ids) | {prompt_end_token_id}
|
|
88
|
+
body_end = ends[-1]
|
|
89
|
+
while body_end > 0 and token_ids[body_end - 1] in special:
|
|
90
|
+
body_end -= 1
|
|
91
|
+
body_start = 0
|
|
92
|
+
while body_start < body_end and token_ids[body_start] in special:
|
|
93
|
+
body_start += 1
|
|
94
|
+
cut = min(over, body_end - body_start)
|
|
95
|
+
if cut <= 0:
|
|
96
|
+
return token_ids
|
|
97
|
+
return token_ids[:body_end - cut] + token_ids[body_end:]
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def special_token_ids(tokenizer) -> set:
|
|
101
|
+
""" Special & added token ids, for HF tokenizers and wrappers w/ a subset of the interface. """
|
|
102
|
+
ids = set(getattr(tokenizer, 'all_special_ids', None) or ())
|
|
103
|
+
get_added_vocab = getattr(tokenizer, 'get_added_vocab', None)
|
|
104
|
+
if get_added_vocab is not None:
|
|
105
|
+
ids |= set(get_added_vocab().values())
|
|
106
|
+
return ids
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _tokenize_keep_prompt_end(
|
|
110
|
+
text: str,
|
|
111
|
+
text_input_ids: torch.Tensor,
|
|
112
|
+
tokenizer: Callable,
|
|
113
|
+
max_length: int,
|
|
114
|
+
prompt_end_token: Union[str, int],
|
|
115
|
+
) -> torch.Tensor:
|
|
116
|
+
""" Re-tokenize a (possibly) truncated `text_input_ids` w/ the prompt shortened instead of the tail cut off. """
|
|
117
|
+
global _num_prompts_truncated
|
|
118
|
+
pad_token_id = tokenizer.pad_token_id
|
|
119
|
+
if bool((text_input_ids == pad_token_id).any()):
|
|
120
|
+
return text_input_ids # padded, nothing was truncated
|
|
121
|
+
prompt_end_token_id = prompt_end_token
|
|
122
|
+
if isinstance(prompt_end_token, str):
|
|
123
|
+
prompt_end_token_id = tokenizer.convert_tokens_to_ids(prompt_end_token)
|
|
124
|
+
special = special_token_ids(tokenizer)
|
|
125
|
+
ends = torch.nonzero(text_input_ids == prompt_end_token_id)
|
|
126
|
+
if len(ends) and all(int(t) in special for t in text_input_ids[:int(ends[0])]):
|
|
127
|
+
# the prompt end survived truncation w/ only special tokens before it (e.g. a task start token as the
|
|
128
|
+
# prompt end), no prompt body to shorten, skip re-tokenizing
|
|
129
|
+
return text_input_ids
|
|
130
|
+
full_ids = tokenizer(text, add_special_tokens=False).input_ids
|
|
131
|
+
if len(full_ids) <= max_length:
|
|
132
|
+
return text_input_ids
|
|
133
|
+
ids = truncate_prompt(full_ids, max_length, prompt_end_token_id, special)
|
|
134
|
+
if len(ids) == len(full_ids):
|
|
135
|
+
return text_input_ids # no prompt (body) to shorten
|
|
136
|
+
_num_prompts_truncated += 1
|
|
137
|
+
n = _num_prompts_truncated
|
|
138
|
+
if n == 10 ** (len(str(n)) - 1): # log the 1st, 10th, 100th, ... (per process / dataloader worker)
|
|
139
|
+
_logger.warning(
|
|
140
|
+
f'Prompt shortened by {len(full_ids) - len(ids)} tokens to fit the target in max_length={max_length} '
|
|
141
|
+
f'({n} prompts shortened so far in this process).')
|
|
142
|
+
ids = ids[:max_length] # target alone too long, the tail is still cut
|
|
143
|
+
num_pad = max_length - len(ids)
|
|
144
|
+
if num_pad:
|
|
145
|
+
pad = [pad_token_id] * num_pad
|
|
146
|
+
ids = pad + ids if getattr(tokenizer, 'padding_side', 'right') == 'left' else ids + pad
|
|
147
|
+
return torch.tensor(ids, dtype=text_input_ids.dtype)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def prepare_text_input(
|
|
151
|
+
text_input,
|
|
152
|
+
tokenizer: Callable,
|
|
153
|
+
max_length: int,
|
|
154
|
+
task_start_token: Optional[str] = None,
|
|
155
|
+
prompt_end_token: Optional[str] = None,
|
|
156
|
+
add_eos_token: bool = True,
|
|
157
|
+
ignore_id: int = -100,
|
|
158
|
+
include_target: bool = True,
|
|
159
|
+
return_dict: bool = True,
|
|
160
|
+
input_key: str = "text_input",
|
|
161
|
+
target_key: str = "text_target",
|
|
162
|
+
):
|
|
163
|
+
"""
|
|
164
|
+
Simple data preprocessing for raw-text data.
|
|
165
|
+
"""
|
|
166
|
+
if task_start_token:
|
|
167
|
+
text_input = task_start_token + text_input
|
|
168
|
+
|
|
169
|
+
if add_eos_token:
|
|
170
|
+
text_input += tokenizer.eos_token
|
|
171
|
+
|
|
172
|
+
text_input_ids = tokenize(text_input, tokenizer=tokenizer, max_length=max_length)
|
|
173
|
+
if prompt_end_token is not None:
|
|
174
|
+
# an over-long prompt (question) is shortened so the prompt end token & target (answer) aren't truncated away
|
|
175
|
+
text_input_ids = _tokenize_keep_prompt_end(text_input, text_input_ids, tokenizer, max_length, prompt_end_token)
|
|
176
|
+
|
|
177
|
+
if include_target:
|
|
178
|
+
text_target_ids = text_input_to_target(text_input_ids, tokenizer, prompt_end_token, ignore_id)
|
|
179
|
+
if return_dict:
|
|
180
|
+
return {input_key: text_input_ids, target_key: text_target_ids}
|
|
181
|
+
else:
|
|
182
|
+
return text_input_ids, text_target_ids
|
|
183
|
+
else:
|
|
184
|
+
# FIXME calculate prompt end pos for validation use (target not needed)
|
|
185
|
+
if return_dict:
|
|
186
|
+
return {input_key: text_input_ids}
|
|
187
|
+
else:
|
|
188
|
+
return text_input_ids
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def create_text_preprocessor(
|
|
192
|
+
tokenizer: Union[str, Callable],
|
|
193
|
+
max_length: int = 1024,
|
|
194
|
+
task_start_token: Optional[str] = None,
|
|
195
|
+
prompt_end_token: Optional[str] = None,
|
|
196
|
+
ignore_id: int = -100,
|
|
197
|
+
include_target: bool = True,
|
|
198
|
+
return_dict: bool = True,
|
|
199
|
+
input_key: str = "text_input",
|
|
200
|
+
target_key: str = "text_target",
|
|
201
|
+
):
|
|
202
|
+
if isinstance(tokenizer, str):
|
|
203
|
+
import transformers
|
|
204
|
+
tokenizer = transformers.AutoTokenizer.from_pretrained(tokenizer)
|
|
205
|
+
|
|
206
|
+
# FIXME just binding prepare_text_input fn for now
|
|
207
|
+
# This is currently tailored to the Donut style image enc + text decoder use case.
|
|
208
|
+
# More functionality / variety required.
|
|
209
|
+
|
|
210
|
+
preprocess_fn = partial(
|
|
211
|
+
prepare_text_input,
|
|
212
|
+
tokenizer=tokenizer,
|
|
213
|
+
max_length=max_length,
|
|
214
|
+
task_start_token=task_start_token,
|
|
215
|
+
prompt_end_token=prompt_end_token,
|
|
216
|
+
ignore_id=ignore_id,
|
|
217
|
+
include_target=include_target,
|
|
218
|
+
return_dict=return_dict,
|
|
219
|
+
input_key=input_key,
|
|
220
|
+
target_key=target_key,
|
|
221
|
+
)
|
|
222
|
+
return preprocess_fn
|
chugr/version.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '0.2.0dev0'
|
chugr/wds/__init__.py
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
from .decode import decode_pdf_pages, decode_image_pages, create_image_decoder, DecodeDoc
|
|
2
|
+
from .filters import detshuffle_v2, map_v2, map_expand_maybe, map_expand_always, flatten_nested
|
|
3
|
+
from .helpers import log_and_continue, expand_urls, get_error_handler
|
|
4
|
+
from .loader import create_loader_wds
|
|
5
|
+
from .pipeline import build_data_pipeline
|
|
6
|
+
from .shardlists import ResampledShardsV2, ShuffledShardList
|
|
7
|
+
from .tariterators import group_by_keys_nothrow, tarfile_to_samples_nothrow
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import ast
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
from typing import Dict
|
|
5
|
+
|
|
6
|
+
from webdataset.shardlists import expand_urls
|
|
7
|
+
|
|
8
|
+
from chugr.common import SplitInfo
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_dataset_size(shards):
|
|
12
|
+
shardlist, _ = expand_urls(shards)
|
|
13
|
+
dir_path = os.path.dirname(shardlist[0])
|
|
14
|
+
|
|
15
|
+
sizes_filename = os.path.join(dir_path, 'sizes.json')
|
|
16
|
+
len_filename = os.path.join(dir_path, '__len__')
|
|
17
|
+
|
|
18
|
+
if os.path.exists(sizes_filename):
|
|
19
|
+
sizes = json.load(open(sizes_filename, 'r'))
|
|
20
|
+
total_size = sum([int(sizes[os.path.basename(shard)]) for shard in shardlist])
|
|
21
|
+
elif os.path.exists(len_filename):
|
|
22
|
+
total_size = ast.literal_eval(open(len_filename, 'r').read())
|
|
23
|
+
else:
|
|
24
|
+
total_size = None # num samples undefined
|
|
25
|
+
|
|
26
|
+
num_shards = len(shardlist)
|
|
27
|
+
|
|
28
|
+
return total_size, num_shards
|
|
29
|
+
|
|
30
|
+
## FIXME this is not working / not completed, parsing _info files is a TODO
|
|
31
|
+
|
|
32
|
+
def _parse_split_info(split: str, info: Dict):
|
|
33
|
+
def _info_convert(dict_info):
|
|
34
|
+
return SplitInfo(
|
|
35
|
+
num_samples=dict_info['num_samples'],
|
|
36
|
+
filenames=tuple(dict_info['filenames']),
|
|
37
|
+
shard_lengths=tuple(dict_info['shard_lengths']),
|
|
38
|
+
name=dict_info['name'],
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
if 'tar' in split or '..' in split:
|
|
42
|
+
split_filenames = expand_urls(split)
|
|
43
|
+
if split_name:
|
|
44
|
+
split_info = info['splits'][split_name]
|
|
45
|
+
if not num_samples:
|
|
46
|
+
_fc = {f: c for f, c in zip(split_info['filenames'], split_info['shard_lengths'])}
|
|
47
|
+
num_samples = sum(_fc[f] for f in split_filenames)
|
|
48
|
+
split_info['filenames'] = tuple(_fc.keys())
|
|
49
|
+
split_info['shard_lengths'] = tuple(_fc.values())
|
|
50
|
+
split_info['num_samples'] = num_samples
|
|
51
|
+
split_info = _info_convert(split_info)
|
|
52
|
+
else:
|
|
53
|
+
split_info = SplitInfo(
|
|
54
|
+
name=split_name,
|
|
55
|
+
num_samples=num_samples,
|
|
56
|
+
filenames=split_filenames,
|
|
57
|
+
)
|
|
58
|
+
else:
|
|
59
|
+
if 'splits' not in info or split not in info['splits']:
|
|
60
|
+
raise RuntimeError(f"split {split} not found in info ({info.get('splits', {}).keys()})")
|
|
61
|
+
split = split
|
|
62
|
+
split_info = info['splits'][split]
|
|
63
|
+
split_info = _info_convert(split_info)
|
|
64
|
+
|
|
65
|
+
return split_info
|