Coverage for / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / data / tools / huggingface_offline.py: 0%
70 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-10-25 05:45 +0800
« prev ^ index » next coverage.py v7.13.1, created at 2026-10-25 05:45 +0800
1# Copyright 2026 Huawei Technologies Co., Ltd
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14# ============================================================================
16"""Download a Hugging Face dataset and convert it to indexed binary data.
18This module is both importable and runnable as a CLI entry point::
20 python -m hyper_parallel.data.tools.huggingface_offline \\
21 --dataset Salesforce/wikitext \\
22 --dataset-subset wikitext-103-raw-v1 \\
23 --output-prefix ./offline_datasets/my_dataset \\
24 --tokenizer gpt2 \\
25 --json-keys text \\
26 --workers 8
27"""
29import argparse
30import os
31from pathlib import Path
32from typing import Any, List
34from hyper_parallel.data.dataset_logging import get_dataset_logger
35from hyper_parallel.data.tools.offline_config import OfflinePreparationConfig
36from hyper_parallel.data.tools.offline_preparation import (
37 prepare_offline_dataset,
38)
40logger = get_dataset_logger(__name__)
43def _download_jsonl(config: OfflinePreparationConfig) -> Path:
44 """Download and normalize the configured Hugging Face split as JSONL."""
45 json_path = config.resolved_json_path()
46 source_path = Path(config.dataset_name_or_path).expanduser()
47 local_suffixes = (".json", ".jsonl", ".json.gz", ".jsonl.gz")
48 if source_path.is_dir() or source_path.name.lower().endswith(local_suffixes):
49 logger.info("Using local JSON dataset input at %s", json_path)
50 return json_path
52 if json_path.is_file():
53 logger.info("Reusing downloaded Hugging Face dataset at %s", json_path)
54 return json_path
56 logger.info(
57 "Downloading Hugging Face dataset %s (config=%s, split=%s)",
58 config.dataset_name_or_path,
59 config.dataset_subset_name,
60 config.dataset_split,
61 )
62 from datasets import load_dataset # pylint: disable=C0415
64 if config.num_proc is not None and config.num_proc <= 0:
65 raise ValueError("num_proc must be greater than zero")
67 load_dataset_kwargs: dict[str, Any] = {
68 "path": config.dataset_name_or_path,
69 "split": config.dataset_split,
70 }
71 optional_load_dataset_kwargs = {
72 "name": config.dataset_subset_name,
73 "revision": config.revision,
74 "cache_dir": config.cache_dir,
75 "data_dir": config.data_dir,
76 "data_files": config.data_files,
77 "num_proc": config.num_proc,
78 }
79 load_dataset_kwargs.update({key: value for key, value in optional_load_dataset_kwargs.items() if value is not None})
80 dataset = load_dataset(**load_dataset_kwargs)
81 keys = config.json_keys_list()
82 missing_keys = [key for key in keys if key not in dataset.column_names]
83 if missing_keys:
84 raise ValueError(
85 f"Dataset {config.dataset_name_or_path} does not contain configured "
86 f"keys {missing_keys}; "
87 f"available columns: {dataset.column_names}"
88 )
89 dataset = dataset.select_columns(keys)
91 json_path.parent.mkdir(parents=True, exist_ok=True)
92 dataset.to_json(
93 str(json_path),
94 orient="records",
95 lines=True,
96 force_ascii=False,
97 )
98 logger.info("Saved Hugging Face dataset to %s", json_path)
99 return json_path
102def _parse_bool(value: str) -> bool:
103 """Parse an explicit command-line boolean value."""
104 normalized = value.strip().lower()
105 if normalized in {"true", "1", "yes", "on"}:
106 return True
107 if normalized in {"false", "0", "no", "off"}:
108 return False
109 raise argparse.ArgumentTypeError(f"Expected a boolean value, but received {value!r}")
112def _add_huggingface_arguments(parser: argparse.ArgumentParser) -> None:
113 """Register Hugging Face source and output arguments."""
114 dataset_group = parser.add_argument_group("dataset")
115 dataset_arguments = (
116 (("--dataset",), {"required": True, "help": "Dataset ID or local JSON/JSONL path."}),
117 (("--dataset-subset",), {"default": None, "help": "Optional dataset subset."}),
118 (("--dataset-split",), {"default": "train", "help": "Dataset split."}),
119 (("--revision",), {"default": None, "help": "Optional dataset revision."}),
120 (("--cache-dir",), {"default": None, "help": "Optional dataset cache directory."}),
121 (("--data-dir",), {"default": None, "help": "Optional repository data directory."}),
122 (("--data-files",), {"nargs": "+", "default": None, "help": "Optional source data files."}),
123 (("--num-proc",), {"type": int, "default": None, "help": "Dataset preparation process count."}),
124 (("--json-keys",), {"nargs": "+", "default": ["text"], "help": "JSON fields to tokenize."}),
125 )
126 for flags, options in dataset_arguments:
127 dataset_group.add_argument(*flags, **options)
128 output_group = parser.add_argument_group("output")
129 output_group.add_argument("--output-prefix", required=True, help="Generated .bin/.idx path prefix.")
130 output_group.add_argument("--download-dir", default=None, help="Raw JSONL download directory.")
133def _add_conversion_arguments(parser: argparse.ArgumentParser) -> None:
134 """Register tokenizer, preprocessing, parallelism, and benchmark arguments."""
135 groups = {
136 "tokenizer": (
137 (("--tokenizer",), {"required": True, "help": "Tokenizer name or local path."}),
138 (("--tokenizer-use-fast",), {"type": _parse_bool, "default": True, "help": "Use fast tokenizer."}),
139 (("--trust-remote-code",), {"action": "store_true", "help": "Allow tokenizer remote code."}),
140 (("--chat-template",), {"default": None, "help": "Optional tokenizer chat template."}),
141 (("--add-special-tokens",), {"nargs": "+", "default": None, "help": "Additional special tokens."}),
142 ),
143 "preprocessing": (
144 (("--split-sentences",), {"action": "store_true", "help": "Split text into sentences."}),
145 (("--keep-newlines",), {"action": "store_true", "help": "Preserve newline runs."}),
146 (("--lang",), {"default": "english", "help": "Punkt language."}),
147 (("--append-eod",), {"type": _parse_bool, "default": True, "help": "Append an EOD token."}),
148 (("--pack-to-seq-len",), {"type": int, "default": None, "help": "Fixed packed sequence length."}),
149 ),
150 "parallelism": (
151 (("--workers",), {"type": int, "default": 8, "help": "Worker process count."}),
152 (("--partitions",), {"type": int, "default": 1, "help": "Data partition count."}),
153 (("--keep-sequential-samples",), {"action": "store_true", "help": "Keep sample order."}),
154 (("--keep-partition-files",), {"action": "store_true", "help": "Keep partition files."}),
155 ),
156 "benchmark": (
157 (("--find-optimal-num-workers",), {"action": "store_true", "help": "Benchmark worker counts."}),
158 (
159 ("--workers-to-check",),
160 {"nargs": "+", "type": int, "default": [16, 32, 64], "help": "Candidate worker counts."},
161 ),
162 (("--max-documents",), {"type": int, "default": 100_000, "help": "Benchmark document limit."}),
163 (("--log-interval",), {"type": int, "default": 1000, "help": "Progress-report interval."}),
164 ),
165 }
166 for title, arguments in groups.items():
167 group = parser.add_argument_group(title)
168 for flags, options in arguments:
169 group.add_argument(*flags, **options)
172def _get_args(argv: List[str] | None = None) -> argparse.Namespace:
173 """Parse the Hugging Face offline preparation CLI arguments."""
174 parser = argparse.ArgumentParser(description="Download a Hugging Face dataset and convert to .bin/.idx")
175 _add_huggingface_arguments(parser)
176 _add_conversion_arguments(parser)
177 return parser.parse_args(argv)
180def main(argv: List[str] | None = None) -> None:
181 """Download, tokenize, and convert a dataset to ``.bin/.idx`` files."""
182 args = _get_args(argv)
184 config = OfflinePreparationConfig(
185 dataset_name_or_path=args.dataset,
186 dataset_subset_name=args.dataset_subset,
187 dataset_split=args.dataset_split,
188 revision=args.revision,
189 cache_dir=args.cache_dir,
190 data_dir=args.data_dir,
191 data_files=args.data_files,
192 num_proc=args.num_proc,
193 json_keys=args.json_keys,
194 output_prefix=args.output_prefix,
195 download_dir=args.download_dir,
196 tokenizer_name_or_path=args.tokenizer,
197 tokenizer_use_fast=args.tokenizer_use_fast,
198 trust_remote_code=args.trust_remote_code,
199 chat_template=args.chat_template,
200 add_special_tokens=args.add_special_tokens,
201 split_sentences=args.split_sentences,
202 keep_newlines=args.keep_newlines,
203 lang=args.lang,
204 workers=args.workers,
205 partitions=args.partitions,
206 append_eod=args.append_eod,
207 pack_to_seq_len=args.pack_to_seq_len,
208 keep_sequential_samples=args.keep_sequential_samples,
209 keep_partition_files=args.keep_partition_files,
210 find_optimal_num_workers=args.find_optimal_num_workers,
211 workers_to_check=args.workers_to_check,
212 max_documents=args.max_documents,
213 log_interval=args.log_interval,
214 )
216 if int(os.environ.get("RANK", "0")) != 0:
217 return
219 _ = _download_jsonl(config)
220 prepare_offline_dataset(config.to_offline_args())
223if __name__ == "__main__":
224 main()