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