make_calib_data.py Source File

make_calib_data.py Source File#

Mobilint SDK qb Compiler: make_calib_data.py Source File
Mobilint SDK qb Compiler v0.12.0.0
MCS002-EN
make_calib_data.py
Go to the documentation of this file.
1
4
5
6from typing import Union, Callable, List, Optional
7from shutil import rmtree
8
9import os
10import numpy as np
11import random
12import yaml
13
14from tqdm import tqdm
15from pathlib import Path
16from .preprocess import preproc_builder
17from .utils_calib import (
18 list_np_files_in_txt,
19 list_np_files_in_json,
20 get_balanced_ids_coco,
21 read_structure,
22 make_calib_dict,
23 save_calib_to_msgpack,
24)
25
26
27
30def get_yaml(yaml_path: str):
31 assert (
32 isinstance(yaml_path, str)
33 and os.path.isfile(yaml_path)
34 and yaml_path.endswith(".yaml")
35 ), f"Got wrong yaml path: {yaml_path}."
36 with open(yaml_path) as f:
37 cfg = yaml.load(f, Loader=yaml.FullLoader)
38 return cfg
39
40
41def make_calib_man(
42 pre_ftn,
43 data_dir: str,
44 save_dir: str,
45 save_name: str,
46 anno_json: str = None,
47 file_format: str = "%012d.jpg",
48 max_size: int = -1,
49 remove_npy: bool = False,
50 seed: int = 2023,
51 save_calib_msg: bool = False,
52 msg_path: str = None,
53):
54 """
55 @brief From images and a user-provided preprocessing function, generate calibration NumPy files and a manifest.
56
57 @param pre_ftn Callable. Pre-processing function that accepts an image path and returns a NumPy array.
58 @param data_dir string. Directory containing the raw calibration images.
59 @param save_dir string. Directory in which to store the generated NumPy files and metadata text file.
60 @param save_name string. Basename for the saved assets. NumPy files are written to
61 @c {save_dir}/{save_name}_npy and the manifest to @c {save_dir}/{save_name}.txt. Defaults to the basename of
62 @p data_dir when omitted.
63 @param anno_json string. Optional COCO-style annotation file. When provided, samples are randomly selected while
64 maintaining class balance.
65 @param file_format string. Filename format that uses @c image_idx (for example "%012d.jpg").
66 @param max_size int. Maximum number of calibration samples. A value of -1 disables the limit.
67 @param remove_npy bool. Remove existing NumPy outputs before generating new data.
68 @param seed int. Random seed applied to sampling and class-balanced selection. Defaults to 2023.
69 @param save_calib_msg bool. Persist the calibration metadata as an MSGpack file in addition to the manifest.
70 @param msg_path string. Explicit MSGpack output path. When omitted, the path is auto-generated by dataset name and
71 sample count.
72 @return string. Path to the generated text file that lists the preprocessed NumPy samples.
73
74 @note Calibration samples are written as float32 NumPy arrays derived from the @p pre_ftn output. When
75 @p max_size is positive and the dataset is larger, a random subset is used. COCO annotations override this sampling
76 to balance categories.
77 """
78 assert isinstance(
79 pre_ftn, Callable
80 ), f"Expected a function for pre_ftn, but got type={type(pre_ftn)}."
81
82 if save_name is None:
83 save_name = os.path.basename(data_dir)
84 save_npy_dir = os.path.join(save_dir, f"{save_name}")
85 save_txt_dir = os.path.join(save_dir, f"{save_name}.txt")
86
87 # Remove pre-existing numpy files if remove_npy = True.
88 if (remove_npy) and (os.path.isdir(save_dir)) and (os.path.isdir(save_npy_dir)):
89 tmp_np_list = [x for x in os.listdir(save_npy_dir) if x.endswith("npy")]
90 print(f"There are {len(tmp_np_list)} npy files in {save_npy_dir}.")
91 rmtree(save_dir)
92 os.makedirs(save_dir, exist_ok=True)
93 os.makedirs(save_npy_dir, exist_ok=True)
94
95 img_path_list = read_structure(data_dir)
96 fname_list = []
97 npy_list = []
98 ids_list = []
99
100 # Limit maximum size of calibration dataset.
101 random.seed(seed)
102 if anno_json is not None:
103 img_path_list, ids_list = get_balanced_ids_coco(
104 anno_json, img_path_list, max_size, fname_format=file_format, seed=seed
105 )
106 dataname = "coco"
107 elif max_size > 0 and len(img_path_list) > max_size:
108 img_path_list = random.sample(img_path_list, max_size)
109 dataname = "any_sampled"
110 else:
111 dataname = "any_all"
112
113 # Collect samples and make/save numpy files.
114 for img_path in tqdm(img_path_list, desc="Making Numpy File"):
115 out = pre_ftn(img_path)
116 if out is None:
117 continue
118 if not isinstance(out, np.ndarray):
119 raise AssertionError(f"Got unexpected tensor type: {type(out)}.", out)
120
121 save_npy = out.astype(np.float32)
122 fname = os.path.basename(img_path)
123 fname_list.append(fname)
124 npy_list.append(save_npy)
125 img_name = Path(fname).stem
126 save_path = os.path.join(save_npy_dir, img_name + ".npy")
127 np.save(save_path, save_npy)
128
129 if save_calib_msg:
130 calib_dict = make_calib_dict(
131 img_path_list, fname_list, npy_list, dataname, ids_list
132 )
133 save_calib_to_msgpack(calib_dict, msg_path)
134
135 # make metadata text for numpy files.
136 list_np_files_in_txt(save_npy_dir, save_txt_dir)
137 return save_txt_dir
138
139
140def make_calib(
141 args_pre: Union[str, dict],
142 data_dir: str,
143 save_dir: str,
144 save_name: str = None,
145 anno_json: str = None,
146 file_format: str = "%012d.jpg",
147 max_size: int = -1,
148 remove_npy: bool = False,
149 seed: int = 2023,
150 save_calib_msg: bool = False,
151 msg_path: str = None,
152) -> str:
153 """
154 @brief Create calibration NumPy files and a manifest using a YAML or dict preprocessing configuration.
155
156 @param args_pre string or dict. Path to a YAML file or an in-memory dictionary describing the preprocessing
157 pipeline. See the preproc_builder class for all supported operators and parameters.
158 @param data_dir string. Directory of calibration images.
159 @param save_dir string. Directory where the generated NumPy files and manifest text file are stored.
160 @param save_name string. Optional basename for outputs. Defaults to the basename of @p data_dir.
161 @param anno_json string. Optional COCO-format annotation file used to sample images with class balance.
162 @param file_format string. Filename template built from @c image_idx (defaults to "%012d.jpg").
163 @param max_size int. Maximum number of calibration samples to generate; -1 keeps all available items.
164 @param remove_npy bool. Remove existing NumPy outputs before running preprocessing.
165 @param seed int. Random seed used for sampling. Defaults to 2023.
166 @param save_calib_msg bool. Save calibration metadata as MSGpack alongside NumPy and text outputs.
167 @param msg_path string. Explicit MSGpack output path. Auto-generated from dataset name and sample count when
168 omitted.
169 @return string. Path to the manifest text file that lists the generated NumPy samples.
170
171 @note The preprocessing pipeline is constructed through @c preproc_builder and executed via @c make_calib_man. Use
172 the YAML schema to combine primitives such as GetImage, Resize, Normalize, Pad, CenterCrop, and SetOrder in a
173 Pre-Order list.
174 """
175 if isinstance(args_pre, str):
176 args_pre = get_yaml(args_pre)
177 elif isinstance(args_pre, dict):
178 pass
179 else:
180 raise TypeError(
181 f"Expected yaml path (str) or cfg dictionary, but got type={type(args_pre)}."
182 )
183
184 pre_process_ftn = preproc_builder(args_pre, to_tensor=False)
185 save_txt_dir = make_calib_man(
186 pre_process_ftn,
187 data_dir,
188 save_dir,
189 save_name,
190 anno_json,
191 file_format,
192 max_size,
193 remove_npy,
194 seed,
195 save_calib_msg,
196 msg_path,
197 )
198 return save_txt_dir
199
200
201def make_calib_llm(
202 args_pre: Union[str, dict],
203 dataset_name: str,
204 subset_list: Union[List[str], str],
205 text_field: str,
206 split: str,
207 save_dir: str,
208 max_calib: int = 512,
209 save_prefix: Optional[str] = None,
210) -> List[str]:
211 """Generate calibration embeddings for LLM models using Hugging Face datasets.
212
213 Args:
214 args_pre: YAML path or preprocessing configuration dictionary. Must set
215 Datatype to "LLM".
216 dataset_name: Name of the Hugging Face dataset to load.
217 subset_list: List of dataset subsets (e.g. language splits) to iterate.
218 split: HF dataset split to use.
219 text_field: Key containing text in dataset entries.
220 save_dir: Root directory where calibration samples are stored.
221 max_calib: Maximum number of calibration samples per subset.
222 save_prefix: Override prefix used in output directory naming. If None,
223 derives from tokenizer directory name when available.
224
225 Returns:
226 List of paths to generated calibration txt files, one per subset.
227 """
228
229 from datasets import load_dataset # Delay import to avoid optional dep cost
230
231 if isinstance(args_pre, str):
232 args_pre = get_yaml(args_pre)
233 elif not isinstance(args_pre, dict):
234 raise TypeError(
235 f"Expected yaml path (str) or cfg dictionary, but got type={type(args_pre)}."
236 )
237
238 pre_process_ftn = preproc_builder(args_pre, to_tensor=False)
239 if getattr(pre_process_ftn, "datatype", None) != "LLM":
240 raise ValueError(
241 "LLM calibration requires Datatype='LLM' in preprocessing config."
242 )
243
244 embedding_stage = next(
245 (fnc for fnc in pre_process_ftn.preprocess if hasattr(fnc, "model_name")),
246 None,
247 )
248 model_name = getattr(embedding_stage, "model_name", None)
249 if save_prefix is None:
250 save_prefix = model_name or "llm"
251
252 os.makedirs(save_dir, exist_ok=True)
253 generated_txt_paths: List[str] = []
254
255 if isinstance(subset_list, str):
256 subset_list = [subset_list]
257
258 for subset_name in subset_list:
259 dataset = load_dataset(dataset_name, subset_name, split=split)
260 lang_suffix = subset_name.split(".")[-1] if "." in subset_name else subset_name
261 target_dir_name = (
262 f"{save_prefix}-{dataset_name.replace('/', '_')}-{lang_suffix}"
263 )
264 target_dir = os.path.join(save_dir, target_dir_name)
265 os.makedirs(target_dir, exist_ok=True)
266
267 cur_num_calib = 0
268 for idx, sample in enumerate(dataset):
269 if isinstance(sample, dict):
270 if text_field not in sample:
271 raise KeyError(
272 f"Key '{text_field}' not found in dataset sample for subset {subset_name}."
273 )
274 sample_input = sample[text_field]
275 else:
276 sample_input = sample
277
278 try:
279 embedded_text = pre_process_ftn(sample_input)
280 except Exception as err:
281 print(f"Skipping dataset item {idx} due to error: {err}")
282 continue
283
284 if embedded_text is None:
285 continue
286
287 if not isinstance(embedded_text, np.ndarray):
288 raise AssertionError(
289 f"LLM preprocessing should return numpy.ndarray, got {type(embedded_text)}."
290 )
291
292 save_path = os.path.join(target_dir, f"inputs_embeds_{cur_num_calib}.npy")
293 np.save(save_path, embedded_text)
294 cur_num_calib += 1
295
296 if cur_num_calib >= max_calib:
297 break
298
299 if cur_num_calib == 0:
300 print(
301 f"No calibration samples were generated for subset {subset_name}; check preprocessing constraints."
302 )
303 continue
304
305 txt_path = os.path.join(save_dir, f"{target_dir_name}.txt")
306 list_np_files_in_txt(target_dir, txt_path)
307 generated_txt_paths.append(txt_path)
308
309 return generated_txt_paths
310
311
312