Coverage for / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / data / indexed / indexed_blended_dataset.py: 0%
82 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# ============================================================================
15"""Standard deterministic weighted Dataset blending."""
17from __future__ import annotations
19import hashlib
20import json
21import os
22from collections import OrderedDict
23from collections.abc import Mapping, Sequence
24from typing import Any
26import numpy as np
28from hyper_parallel.data.dataset_logging import get_dataset_logger
29from hyper_parallel.data.indexed.indexed_data_config import GPTDatasetConfig
30from hyper_parallel.data.indexed.indexed_helpers import build_blending_indices
32logger = get_dataset_logger(__name__)
35class BlendedDataset:
36 """Expose several Datasets through deterministic weighted sample indices."""
38 def __init__(
39 self,
40 datasets: Sequence[Any],
41 weights: Sequence[float],
42 size: int,
43 config: GPTDatasetConfig,
44 ) -> None:
45 """Validate the inputs and create weighted sample indices."""
46 if not datasets or len(datasets) != len(weights):
47 raise ValueError("datasets and weights must be non-empty and have the same length")
48 if len(datasets) >= np.iinfo(np.int16).max:
49 raise ValueError("number of datasets must be less than 32767")
50 if not all(isinstance(dataset, type(datasets[0])) for dataset in datasets):
51 raise ValueError("all datasets must be of the same type")
52 self.datasets = list(datasets)
53 self.weights = _normalize_weights(weights)
54 self.size = size
55 self.config = config
56 self.unique_identifiers = OrderedDict()
57 self.unique_identifiers["class"] = type(self).__name__
58 self.unique_identifiers["datasets"] = self._collect_dataset_identifiers()
59 self.unique_identifiers["weights"] = self.weights
60 self.unique_identifiers["size"] = self.size
61 self.unique_description = json.dumps(
62 self.unique_identifiers,
63 indent=4,
64 default=lambda value: value.unique_identifiers,
65 )
66 self.unique_description_hash = hashlib.md5(
67 self.unique_description.encode("utf-8")
68 ).hexdigest()
69 self.dataset_index, self.dataset_sample_index = self._build_indices()
71 def _collect_dataset_identifiers(self) -> list[Any]:
72 """Collect component Dataset cache identities."""
73 identifiers = [dataset.unique_identifiers for dataset in self.datasets]
74 return identifiers
76 def __len__(self) -> int:
77 """Return the requested blended sample count."""
78 return self.size
80 def __getitem__(self, index: int) -> Mapping[str, Any]:
81 """Read one sample from the Dataset selected by the blend indices."""
82 dataset_id = int(self.dataset_index[index])
83 sample_id = int(self.dataset_sample_index[index])
84 sample = {"dataset_id": dataset_id, **self.datasets[dataset_id][sample_id]}
85 return sample
87 def _build_indices(self) -> tuple[np.ndarray, np.ndarray]:
88 """Load or build the top-level blend index cache."""
89 cache_directory = self.config.path_to_cache
90 if cache_directory is None:
91 logger.debug(
92 "Building blended indices in memory: size=%d, sources=%d, weights are %s",
93 self.size, len(self.weights), self.weights[:3],
94 )
95 indices = self._build_indices_in_memory()
96 return indices
98 cache_prefix = os.path.join(
99 cache_directory,
100 f"{self.unique_description_hash}-{type(self).__name__}",
101 )
102 description_path = f"{cache_prefix}-description.txt"
103 dataset_index_path = f"{cache_prefix}-dataset_index.npy"
104 sample_index_path = f"{cache_prefix}-dataset_sample_index.npy"
105 cache_paths = (description_path, dataset_index_path, sample_index_path)
106 if all(os.path.isfile(path) for path in cache_paths):
107 logger.debug("Loading blended index cache: prefix=%s", cache_prefix)
108 dataset_index = np.load(dataset_index_path, allow_pickle=True, mmap_mode="r")
109 sample_index = np.load(sample_index_path, allow_pickle=True, mmap_mode="r")
110 return dataset_index, sample_index
112 dataset_index, sample_index = self._build_indices_in_memory()
113 os.makedirs(cache_directory, exist_ok=True)
114 with open(description_path, "w", encoding="utf-8") as description_file:
115 description_file.write(self.unique_description)
116 np.save(dataset_index_path, dataset_index, allow_pickle=True)
117 np.save(sample_index_path, sample_index, allow_pickle=True)
118 logger.debug("Saved blended index cache: prefix=%s", cache_prefix)
119 return dataset_index, sample_index
121 def _build_indices_in_memory(self) -> tuple[np.ndarray, np.ndarray]:
122 """Build a deterministic weighted source schedule."""
123 dataset_index = np.empty(self.size, dtype=np.int16)
124 sample_index = np.empty(self.size, dtype=np.int64)
125 build_blending_indices(
126 dataset_index=dataset_index,
127 dataset_sample_index=sample_index,
128 weights=self.weights,
129 )
130 requested_counts = np.bincount(dataset_index, minlength=len(self.datasets))
131 for dataset_id, requested_count in enumerate(requested_counts):
132 dataset_size = len(self.datasets[dataset_id])
133 if requested_count > dataset_size:
134 raise ValueError(
135 f"Dataset {dataset_id} has only {dataset_size} samples, "
136 f"but the blend requested {requested_count} samples"
137 )
138 return dataset_index, sample_index
141def _normalize_weights(weights: Sequence[float]) -> list[float]:
142 """Validate and normalize positive blend weights."""
143 if not weights or any(weight <= 0.0 for weight in weights):
144 raise ValueError("Dataset blend weights must be positive")
145 weight_array = np.asarray(weights, dtype=np.float64)
146 normalized_weights = (weight_array / np.sum(weight_array)).tolist()
147 return normalized_weights