streaming-community 0.14.0__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.
Files changed (133) hide show
  1. simulation/__init__.py +4 -0
  2. simulation/core/__init__.py +4 -0
  3. simulation/core/create_index.py +86 -0
  4. simulation/core/last_used_ordered_set.py +37 -0
  5. simulation/core/main.py +243 -0
  6. simulation/core/node_tracker.py +224 -0
  7. simulation/core/shard_downloads.py +153 -0
  8. simulation/core/shuffle_quality.py +164 -0
  9. simulation/core/sim_dataset.py +573 -0
  10. simulation/core/sim_spanner.py +36 -0
  11. simulation/core/sim_time.py +321 -0
  12. simulation/core/sim_world.py +55 -0
  13. simulation/core/utils.py +167 -0
  14. simulation/core/yaml_processing.py +207 -0
  15. simulation/interfaces/__init__.py +4 -0
  16. simulation/interfaces/interface_utils.py +111 -0
  17. simulation/interfaces/sim_cli.py +122 -0
  18. simulation/interfaces/sim_script.py +113 -0
  19. simulation/interfaces/sim_ui.py +410 -0
  20. simulation/interfaces/widgets.py +448 -0
  21. simulation/launcher.py +14 -0
  22. streaming/__init__.py +30 -0
  23. streaming/_install_check.py +34 -0
  24. streaming/_version.py +6 -0
  25. streaming/base/__init__.py +15 -0
  26. streaming/base/array.py +110 -0
  27. streaming/base/batching/__init__.py +45 -0
  28. streaming/base/batching/device_per_stream.py +226 -0
  29. streaming/base/batching/per_stream.py +158 -0
  30. streaming/base/batching/random.py +73 -0
  31. streaming/base/batching/stratified.py +184 -0
  32. streaming/base/compression.py +258 -0
  33. streaming/base/constant.py +36 -0
  34. streaming/base/converters/__init__.py +9 -0
  35. streaming/base/converters/dataframe_to_mds.py +328 -0
  36. streaming/base/dataloader.py +100 -0
  37. streaming/base/dataset.py +1531 -0
  38. streaming/base/distributed.py +129 -0
  39. streaming/base/format/__init__.py +42 -0
  40. streaming/base/format/base/__init__.py +8 -0
  41. streaming/base/format/base/reader.py +400 -0
  42. streaming/base/format/base/writer.py +533 -0
  43. streaming/base/format/index.py +15 -0
  44. streaming/base/format/json/__init__.py +9 -0
  45. streaming/base/format/json/encodings.py +86 -0
  46. streaming/base/format/json/reader.py +123 -0
  47. streaming/base/format/json/writer.py +119 -0
  48. streaming/base/format/mds/__init__.py +9 -0
  49. streaming/base/format/mds/encodings.py +800 -0
  50. streaming/base/format/mds/reader.py +149 -0
  51. streaming/base/format/mds/writer.py +144 -0
  52. streaming/base/format/xsv/__init__.py +9 -0
  53. streaming/base/format/xsv/encodings.py +127 -0
  54. streaming/base/format/xsv/reader.py +275 -0
  55. streaming/base/format/xsv/writer.py +270 -0
  56. streaming/base/hashing.py +68 -0
  57. streaming/base/local.py +78 -0
  58. streaming/base/partition/__init__.py +70 -0
  59. streaming/base/partition/orig.py +181 -0
  60. streaming/base/partition/relaxed.py +93 -0
  61. streaming/base/registry_utils.py +199 -0
  62. streaming/base/sampling.py +80 -0
  63. streaming/base/shared/__init__.py +17 -0
  64. streaming/base/shared/array.py +66 -0
  65. streaming/base/shared/barrier.py +167 -0
  66. streaming/base/shared/memory.py +128 -0
  67. streaming/base/shared/prefix.py +305 -0
  68. streaming/base/shared/scalar.py +38 -0
  69. streaming/base/shuffle/__init__.py +48 -0
  70. streaming/base/shuffle/naive.py +40 -0
  71. streaming/base/shuffle/py1br.py +93 -0
  72. streaming/base/shuffle/py1e.py +144 -0
  73. streaming/base/shuffle/py1s.py +131 -0
  74. streaming/base/shuffle/py2s.py +146 -0
  75. streaming/base/spanner.py +59 -0
  76. streaming/base/storage/__init__.py +36 -0
  77. streaming/base/storage/download.py +907 -0
  78. streaming/base/storage/upload.py +1296 -0
  79. streaming/base/stream.py +522 -0
  80. streaming/base/util.py +605 -0
  81. streaming/base/world.py +163 -0
  82. streaming/multimodal/__init__.py +8 -0
  83. streaming/multimodal/convert/__init__.py +4 -0
  84. streaming/multimodal/convert/laion/__init__.py +4 -0
  85. streaming/multimodal/convert/laion/laion400m/__init__.py +4 -0
  86. streaming/multimodal/convert/laion/laion400m/convert_and_upload.py +290 -0
  87. streaming/multimodal/convert/webvid/__init__.py +4 -0
  88. streaming/multimodal/convert/webvid/crawl_webvid.py +165 -0
  89. streaming/multimodal/convert/webvid/crawl_webvid_subsets.py +233 -0
  90. streaming/multimodal/convert/webvid/extract_webvid_videos.py +97 -0
  91. streaming/multimodal/webvid.py +409 -0
  92. streaming/py.typed +0 -0
  93. streaming/text/__init__.py +10 -0
  94. streaming/text/c4.py +162 -0
  95. streaming/text/convert/__init__.py +4 -0
  96. streaming/text/convert/c4.py +162 -0
  97. streaming/text/convert/enwiki/__init__.py +0 -0
  98. streaming/text/convert/enwiki/mds/__init__.py +0 -0
  99. streaming/text/convert/enwiki/mds/create_pretraining_data.py +453 -0
  100. streaming/text/convert/enwiki/mds/make_train_parallel.py +122 -0
  101. streaming/text/convert/enwiki/mds/merge_shard_groups.py +74 -0
  102. streaming/text/convert/enwiki/mds/pick_eval_samples.py +72 -0
  103. streaming/text/convert/enwiki/mds/tokenization.py +408 -0
  104. streaming/text/convert/enwiki/tfrecord/__init__.py +0 -0
  105. streaming/text/convert/enwiki/tfrecord/count_samples.py +35 -0
  106. streaming/text/convert/enwiki/tfrecord/create_pretraining_data.py +428 -0
  107. streaming/text/convert/enwiki/tfrecord/make_train_parallel.py +37 -0
  108. streaming/text/convert/enwiki/tfrecord/pick_eval_samples.py +125 -0
  109. streaming/text/convert/enwiki/tfrecord/tokenization.py +408 -0
  110. streaming/text/convert/enwiki_text.py +126 -0
  111. streaming/text/convert/pile.py +222 -0
  112. streaming/text/enwiki.py +145 -0
  113. streaming/text/pile.py +164 -0
  114. streaming/vision/__init__.py +11 -0
  115. streaming/vision/ade20k.py +141 -0
  116. streaming/vision/base.py +176 -0
  117. streaming/vision/cifar10.py +73 -0
  118. streaming/vision/coco.py +135 -0
  119. streaming/vision/convert/__init__.py +4 -0
  120. streaming/vision/convert/ade20k.py +183 -0
  121. streaming/vision/convert/base.py +68 -0
  122. streaming/vision/convert/cifar10.py +88 -0
  123. streaming/vision/convert/coco.py +247 -0
  124. streaming/vision/convert/fake_cifar10.py +51 -0
  125. streaming/vision/convert/imagenet.py +169 -0
  126. streaming/vision/imagenet.py +73 -0
  127. streaming_community-0.14.0.dist-info/METADATA +475 -0
  128. streaming_community-0.14.0.dist-info/RECORD +133 -0
  129. streaming_community-0.14.0.dist-info/WHEEL +5 -0
  130. streaming_community-0.14.0.dist-info/entry_points.txt +2 -0
  131. streaming_community-0.14.0.dist-info/licenses/LICENSE +201 -0
  132. streaming_community-0.14.0.dist-info/licenses/NOTICE +9 -0
  133. streaming_community-0.14.0.dist-info/top_level.txt +2 -0
simulation/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ # Copyright 2022-2024 MosaicML Streaming authors
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """Streaming simulation for throughput, network downloads, and shuffle quality."""
@@ -0,0 +1,4 @@
1
+ # Copyright 2022-2024 MosaicML Streaming authors
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """Core classes and functions for streaming simulation."""
@@ -0,0 +1,86 @@
1
+ # Copyright 2022-2024 MosaicML Streaming authors
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """Create a dataset index file from input parameters."""
5
+
6
+ import json
7
+ import logging
8
+ import os
9
+ import random
10
+ import string
11
+ from typing import Optional
12
+
13
+ from streaming.base.format import get_index_basename
14
+
15
+ logger = logging.getLogger(__name__)
16
+ logging.basicConfig(level=logging.INFO)
17
+
18
+
19
+ def get_random_foldername() -> str:
20
+ """Generate random folder name to store the index file in.
21
+
22
+ Returns:
23
+ str: random alphanumeric folder name.
24
+ """
25
+ return ''.join(
26
+ random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits)
27
+ for _ in range(16))
28
+
29
+
30
+ def create_stream_index(shards: int, samples_per_shard: int, avg_raw_shard_size: int,
31
+ avg_zip_shard_size: Optional[int]) -> str:
32
+ """Create dataset index file from input parameters.
33
+
34
+ Args:
35
+ shards (int): Number of shards.
36
+ samples_per_shard (int): Number of samples per shard.
37
+ avg_raw_shard_size (int): Average raw shard size.
38
+ avg_zip_shard_size (int): Average compressed shard size.
39
+
40
+ Returns:
41
+ local path to created index file for stream.
42
+ """
43
+ index_data = {'version': 2, 'shards': []}
44
+
45
+ shards_list = []
46
+ for _ in range(shards):
47
+ shard_data = {
48
+ 'column_encodings': [],
49
+ 'column_names': [],
50
+ 'column_sizes': [],
51
+ 'format': 'mds',
52
+ 'raw_data': {
53
+ 'basename': '',
54
+ 'bytes': avg_raw_shard_size,
55
+ 'hashes': {}
56
+ },
57
+ 'hashes': [],
58
+ 'samples': samples_per_shard,
59
+ 'size_limit': avg_raw_shard_size,
60
+ 'version': 2,
61
+ 'zip_data': None,
62
+ 'compression': None
63
+ }
64
+ if avg_zip_shard_size is not None:
65
+ shard_data['zip_data'] = {'basename': '', 'bytes': avg_zip_shard_size, 'hashes': {}}
66
+ shard_data['compression'] = ''
67
+ shards_list.append(shard_data)
68
+
69
+ index_data['shards'] = shards_list
70
+
71
+ # Try making the directory for the stream's index.json file
72
+ foldername = get_random_foldername() + '_indexcreated'
73
+ try:
74
+ os.mkdir(foldername)
75
+ except FileExistsError:
76
+ logger.warning(' Folder already exists, trying again...')
77
+ foldername = get_random_foldername()
78
+ os.mkdir(foldername)
79
+
80
+ index_basename = get_index_basename()
81
+ index_path = os.path.join(foldername, index_basename)
82
+
83
+ with open(index_path, 'w') as f:
84
+ json.dump(index_data, f)
85
+
86
+ return index_path
@@ -0,0 +1,37 @@
1
+ # Copyright 2022-2024 MosaicML Streaming authors
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """An ordered set that can be used as an LRU cache."""
5
+
6
+ from collections import OrderedDict
7
+ from typing import Any
8
+
9
+
10
+ class LastUsedOrderedSet(OrderedDict):
11
+ """An ordered dict that can be used as an LRU cache.
12
+
13
+ This is a subclass of OrderedDict, with some LRU-specific functions and all values as ``None``.
14
+ """
15
+
16
+ def setitem(self, key: Any, move_to_end: bool = True):
17
+ """Set/add an item.
18
+
19
+ Args:
20
+ key (Any): key to be added.
21
+ move_to_end (bool, optional): whether to move the item to the end, signifying most
22
+ recent access. Defaults to ``True``.
23
+ """
24
+ super().__setitem__(key, None)
25
+ self.move_to_end(key, last=move_to_end)
26
+
27
+ def popLRU(self):
28
+ """Pop the least recently used item (located at the front)."""
29
+ return self.popitem(last=False)[0]
30
+
31
+ def setuse(self, key: Any):
32
+ """Mark an item as used, moving it to the end.
33
+
34
+ Args:
35
+ key (Any): key of element to move to the end, signifying most recent access.
36
+ """
37
+ self.setitem(key)
@@ -0,0 +1,243 @@
1
+ # Copyright 2022-2024 MosaicML Streaming authors
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """Main simulation function, simulating bytes downloaded and time taken each training step."""
5
+
6
+ import logging
7
+ import time
8
+ from typing import Generator, Union
9
+
10
+ import numpy as np
11
+ from numpy.typing import NDArray
12
+
13
+ from simulation.core.node_tracker import NodeTracker
14
+ from simulation.core.shard_downloads import run_cache_limit, simulate_shard_downloads
15
+ from simulation.core.sim_dataset import SimulationDataset
16
+ from simulation.core.sim_time import Time
17
+ from simulation.core.utils import bytes_to_time, get_batches_epochs, time_to_bytes
18
+
19
+ logger = logging.getLogger(__name__)
20
+ logging.basicConfig(level=logging.INFO)
21
+
22
+
23
+ def simulate(
24
+ dataset: SimulationDataset,
25
+ time_per_sample: float,
26
+ node_network_bandwidth: int,
27
+ max_duration: Time,
28
+ generator: bool = False
29
+ ) -> Generator[Union[tuple[int, float, int], tuple[float, int], tuple[NDArray, NDArray, float,
30
+ int]], None, None]:
31
+ """Simulates step time and downloads using streaming for the specified input parameters.
32
+
33
+ At each training step, the simulation does the following:
34
+ * gets the shards containing the current batch's samples.
35
+ * for each node, downloads shards if they are not present (round-robin through workers).
36
+ * predownloads more shards during model process time plus extra time from previous step.
37
+ * tracks the time the the step took as well as downloaded bytes.
38
+
39
+ Key Notes and Assumptions:
40
+ * assume that batch time is solely made up of two things: batch shard download wait time
41
+ and batch processing time
42
+ * loop through workers in a node round-robin style for batches and for downloads
43
+ * assume each node has a separate, uniform network bandwidth
44
+ * the batch has to wait until all nodes have downloaded the shards containing batch samples.
45
+ * for shard eviction itself, use LRU shard eviction to take out the least recently used
46
+ shard, per node.
47
+ * shards are shared across devices on a single node, but nodes do not share shards between
48
+ each other.
49
+ * if a shard is unavailable, we download shards round-robin until we have it.
50
+ * if a shard is available in a node, we just use it.
51
+
52
+ Args:
53
+ dataset (SimulationDataset): SimulationDataset object created based on input params/yaml
54
+ time_per_sample (float): time to process one sample on one device (seconds)
55
+ node_network_bandwidth (int): network bandwidth per node (bytes/s)
56
+ max_duration (Time): max duration of simulation. Defaults to ``None``.
57
+ generator (bool): True if we yield throughput and shard_download one step at a time.
58
+
59
+ Returns:
60
+ Generator[Union[Tuple[int, float, int],
61
+ Tuple[NDArray, NDArray, float, int],
62
+ Tuple[float, int]], None, None]: either a tuple of step number, step time, and
63
+ downloaded bytes, a tuple of startup time and min needed cache limit,
64
+ (both when generator=True), or a tuple of all step times, downloaded bytes,
65
+ startup_time, and min needed cache limit.
66
+ """
67
+ # tracking startup time, which includes SimulationDataset instantiation time.
68
+ start_time = time.time()
69
+ startup_time = dataset.get_instantiation_time()
70
+
71
+ # Get batches, epochs, total batches from dataset and provided time info.
72
+ batches_per_epoch, epochs, total_batches = get_batches_epochs(dataset, max_duration)
73
+
74
+ # Retrieve streaming and dataset information from SimulationDataset.
75
+ physical_nodes = dataset.get_nodes()
76
+ devices = dataset.get_devices()
77
+ workers = dataset.get_workers()
78
+ device_batch_size = dataset.get_batch_size()
79
+ predownload = dataset.get_predownload()
80
+ cache_limit = dataset.get_cache_limit()
81
+ total_shards = dataset.get_num_shards()
82
+ raw_shard_sizes = dataset.get_raw_shard_sizes()
83
+ zip_shard_sizes = dataset.get_zip_shard_sizes()
84
+ # dataset's spanner object maps global sample id to shard id.
85
+ sample_to_shard = dataset.get_spanner()
86
+
87
+ # Initialize NodeTracker objects for each node. These keep track of shards, worker downloads,
88
+ # cache usage, shard usage ranges, etc. for each node.
89
+ nodes = []
90
+ for _ in range(physical_nodes):
91
+ nodes.append(
92
+ NodeTracker(workers, devices, predownload, device_batch_size, total_shards,
93
+ cache_limit))
94
+
95
+ # Time for the global batch is just device batch size * time per sample.
96
+ # We assume all devices process their microbatch perfectly in parallel.
97
+ avg_batch_process_time = device_batch_size * time_per_sample
98
+
99
+ notification_batches = int(batches_per_epoch) / 20
100
+
101
+ # Simulate training by looping over epochs and batches.
102
+
103
+ # Track time and downloads at each step.
104
+ step_times = []
105
+ step_downloads = []
106
+
107
+ for epoch in range(epochs):
108
+
109
+ # Get the samples, divided up per node, for this epoch.
110
+ samples_per_node = dataset.get_samples_per_node(epoch, 0)
111
+
112
+ # Set the samples for each node for this epoch.
113
+ for node_id, node in enumerate(nodes):
114
+ node.samples = samples_per_node[node_id]
115
+ node.initialize_worker_downloads(sample_to_shard)
116
+
117
+ # Track which sample we are currently on, as a worker id. We round-robin over workers.
118
+ worker_sample_index = 0
119
+
120
+ # if first epoch, add time so far to startup time
121
+ if epoch == 0:
122
+ startup_time += time.time() - start_time
123
+
124
+ # Iterate over batches
125
+ for batch in range(batches_per_epoch):
126
+
127
+ step_num = batch + (batches_per_epoch * epoch)
128
+
129
+ # If we are at the last batch, exit.
130
+ if step_num >= total_batches:
131
+ break
132
+
133
+ # Log progress every notification_batches interval.
134
+ if (batch + 1) % notification_batches == 0:
135
+ logger.info(
136
+ f' Epoch: {str(epoch + 1)} | Batch {str(batch + 1)}/{str(batches_per_epoch)}')
137
+
138
+ # We round-robin over workers per device. The current batch's worker is the same
139
+ # across every device.
140
+ curr_worker = batch % workers
141
+ # Track how long each node takes to download the shards that the current batch needs.
142
+ node_batch_download_times = np.array([0] * physical_nodes)
143
+ # Track how many total bytes are downloaded in this batch by all nodes.
144
+ downloaded_bytes = 0
145
+
146
+ # Get current samples and download samples for each node, for this batch.
147
+ for node_id, node in enumerate(nodes):
148
+
149
+ shards_needed, shards_present = node.get_current_batch_shards(
150
+ curr_worker, worker_sample_index, sample_to_shard)
151
+ # Mark all shards present as accessed most recently in this node.
152
+ node.set_shards_used(shards_present, step_num)
153
+ # Push the predownload for the current batch workers ahead by device_batch_size.
154
+ node.update_worker_predownloads(curr_worker, worker_sample_index, sample_to_shard)
155
+ # Track bytes downloaded by this node.
156
+ node_downloaded_bytes = 0
157
+
158
+ # Because we assume downloads also round-robin over workers, we can download shards
159
+ # other than the current batch's shards while looking for current batch's shards.
160
+ while len(shards_needed) > 0:
161
+ download_outcome, download_size = \
162
+ simulate_shard_downloads(node,
163
+ raw_shard_sizes,
164
+ zip_shard_sizes,
165
+ current_batch_downloads=True,
166
+ step_num=step_num,
167
+ cache_limit=cache_limit,
168
+ shards_needed=shards_needed)
169
+ if download_outcome == 'downloaded':
170
+ node_downloaded_bytes += download_size
171
+ downloaded_bytes += download_size
172
+ elif download_outcome == 'present':
173
+ # If the shard was already present in the node, continue downloading.
174
+ pass
175
+ else:
176
+ # If no shard downloads are left in the node, stop downloading.
177
+ break
178
+
179
+ # Calculate how much time this node spent downloading shards
180
+ node_batch_download_times[node_id] = bytes_to_time(node_downloaded_bytes,
181
+ node_network_bandwidth)
182
+
183
+ # The node that took the longest to download shards is the bottleneck. All other nodes
184
+ # use the extra time to continue downloading.
185
+ slowest_download_time = np.max(node_batch_download_times)
186
+
187
+ # If we are on the first step, add slowest_download_time to startup time
188
+ if epoch == 0 and batch == 0:
189
+ startup_time += slowest_download_time
190
+
191
+ # Iterate over nodes again to continue downloading shards.
192
+ for node_id, node in enumerate(nodes):
193
+ # The download time each node has is the avg_batch_process_time plus the extra time
194
+ # the node has from finishing earlier than the slowest node.
195
+ download_time_left = avg_batch_process_time + (slowest_download_time -
196
+ node_batch_download_times[node_id])
197
+ # Get number of bytes we can download in download_time_left.
198
+ # We also include any partially downloaded bytes from previous steps.
199
+ download_bytes_left = time_to_bytes(download_time_left, node_network_bandwidth) + \
200
+ node.partial_shard_bytes
201
+
202
+ while True:
203
+ download_outcome, download_size = \
204
+ simulate_shard_downloads(node,
205
+ raw_shard_sizes,
206
+ zip_shard_sizes,
207
+ current_batch_downloads=False,
208
+ step_num=step_num,
209
+ cache_limit=cache_limit,
210
+ download_bytes_left=download_bytes_left)
211
+ if download_outcome == 'downloaded':
212
+ downloaded_bytes += download_size
213
+ download_bytes_left -= download_size
214
+ elif download_outcome == 'present':
215
+ pass
216
+ else:
217
+ # If no shard downloads are left in the node, or we could only partially
218
+ # download a shard, stop downloading.
219
+ break
220
+
221
+ # Yield or store step number, time and download for this step
222
+ if generator:
223
+ yield step_num, slowest_download_time + avg_batch_process_time, downloaded_bytes
224
+ else:
225
+ step_times.append(slowest_download_time + avg_batch_process_time)
226
+ step_downloads.append(downloaded_bytes)
227
+
228
+ # Increment worker_sample_index by device_batch_size if we are at the last worker.
229
+ # As we round-robin over workers, the sample index per worker is increased as we loop
230
+ # through all workers.
231
+ if curr_worker == workers - 1:
232
+ worker_sample_index += device_batch_size
233
+
234
+ # Simulation is finished. Calculate needed cache limit.
235
+ min_cache_limit = run_cache_limit(nodes, raw_shard_sizes)
236
+
237
+ # Yield results.
238
+ if not generator:
239
+ step_times = np.array(step_times)
240
+ step_downloads = np.array(step_downloads)
241
+ yield step_times, step_downloads, startup_time, min_cache_limit
242
+ else:
243
+ yield startup_time, min_cache_limit
@@ -0,0 +1,224 @@
1
+ # Copyright 2022-2024 MosaicML Streaming authors
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """Class for tracking node information during simulation."""
5
+
6
+ from typing import Optional
7
+
8
+ import numpy as np
9
+ from numpy.typing import NDArray
10
+ from sortedcollections import OrderedSet
11
+
12
+ from simulation.core.last_used_ordered_set import LastUsedOrderedSet
13
+ from simulation.core.utils import remove_padded_samples
14
+ from streaming.base.spanner import Spanner
15
+
16
+
17
+ class NodeTracker():
18
+ """Tracker for node information during simulation.
19
+
20
+ Args:
21
+ workers (int): The number of workers.
22
+ devices (int): The number of devices.
23
+ predownload (int): The number of samples to predownload.
24
+ device_batch_size (int): The device batch size.
25
+ total_shards (int): Total number of shards in the dataset.
26
+ cache_limit (Optional[int]): The cache limit for the node. Defaults to ``None``.
27
+ """
28
+
29
+ def __init__(self,
30
+ workers: int,
31
+ devices: int,
32
+ predownload: int,
33
+ device_batch_size: int,
34
+ total_shards: int,
35
+ cache_limit: Optional[int] = None):
36
+
37
+ self.shards = LastUsedOrderedSet()
38
+ self.all_shards = set()
39
+ self.cache_usage = 0
40
+ self.partial_shard_bytes = 0
41
+ self.worker_download_index = 0
42
+ self.devices = devices
43
+ self.workers = workers
44
+ self.total_workers = workers * devices
45
+ self.device_batch_size = device_batch_size
46
+ self.predownload = predownload
47
+ self.cache_limit = cache_limit
48
+ self.worker_downloads = []
49
+ self.shard_access_starts = np.full(total_shards, -1)
50
+ self.shard_access_ends = np.full(total_shards, -1)
51
+
52
+ # Use the set_epoch_samples method every epoch to set the node's samples.
53
+ self.samples = None
54
+
55
+ def initialize_worker_downloads(self, sample_to_shard: Spanner):
56
+ """Initialize worker downloads, making shards in the predownload sample range available.
57
+
58
+ Args:
59
+ sample_to_shard (Spanner): The mapping from samples to shards.
60
+ """
61
+ # For downloads, we round-robin over devices first, then workers.
62
+ if self.samples is not None:
63
+ for worker in range(self.workers):
64
+ for device in range(self.devices):
65
+ download_samples = remove_padded_samples(
66
+ self.samples[device, worker, :self.predownload])
67
+ # Get the shards these samples correspond to, maintaining access order.
68
+ download_shards = OrderedSet(
69
+ [sample_to_shard[sample] for sample in download_samples])
70
+ self.worker_downloads.append(download_shards)
71
+ else:
72
+ raise AttributeError('Must set node samples before accessing them.')
73
+
74
+ def set_shards_used(self, shards: set, step_num: int):
75
+ """Mark a set of shards as recently used.
76
+
77
+ Args:
78
+ shards (set): The shards to set as used.
79
+ step_num (int): The current step number.
80
+ """
81
+ for shard in shards:
82
+ self.shards.setuse(shard)
83
+ # For any shard access, we are accessing the shard so we need the shard until
84
+ # at least the next step begins. Adding 0.5 ensures that we evict shards
85
+ # after they are used for the last time, but before they are replaced by
86
+ # new downloads in the next step.
87
+ self.shard_access_ends[shard] = step_num + 0.5
88
+
89
+ def add_shard(self, shard: int, used: bool = True):
90
+ """Add a shard to the node.
91
+
92
+ Args:
93
+ shard (int): The shard to add.
94
+ used (bool): Whether the shard is used when added.
95
+ """
96
+ self.shards.setitem(shard, used)
97
+ self.all_shards.add(shard)
98
+
99
+ def get_all_shards(self):
100
+ """Get all the shards in the node."""
101
+ return self.all_shards
102
+
103
+ def evict_shard(self) -> int:
104
+ """Evict a shard.
105
+
106
+ Returns:
107
+ int: The evicted shard.
108
+ """
109
+ evicted_shard = self.shards.popLRU()
110
+ return evicted_shard
111
+
112
+ def evict_until_satisfied(self, incoming_shard_size: int, raw_shard_sizes: NDArray[np.int64]):
113
+ """Evict shards until the node has enough space to download the incoming shard.
114
+
115
+ Args:
116
+ incoming_shard_size (int): The size of the incoming shard.
117
+ raw_shard_sizes (NDArray[np.int64]): The raw shard sizes.
118
+ """
119
+ # We evict shards until the incoming shard fits into the node's cache.
120
+ if self.cache_limit is not None:
121
+ while self.cache_usage + incoming_shard_size > self.cache_limit:
122
+ evicted_shard = self.evict_shard()
123
+ self.cache_usage -= int(raw_shard_sizes[evicted_shard])
124
+
125
+ def increment_worker_download_index(self):
126
+ """Increment the worker download index."""
127
+ self.worker_download_index = (self.worker_download_index + 1) % \
128
+ (self.workers * self.devices)
129
+
130
+ def get_worker_download(self,
131
+ worker: Optional[int] = None,
132
+ device: Optional[int] = None,
133
+ index: Optional[int] = None) -> OrderedSet:
134
+ """Get the shard downloads for a worker on a specific device.
135
+
136
+ Args:
137
+ worker (Optional[int]): The worker index.
138
+ device (Optional[int]): The device index the worker is on.
139
+ index (Optional[int]): The index of the worker download for direct access.
140
+
141
+ Returns:
142
+ OrderedSet: The shard downloads, in order, for this worker.
143
+ """
144
+ if index is not None:
145
+ # Directly access worker_downloads through an index.
146
+ return self.worker_downloads[index]
147
+ elif worker is not None and device is not None:
148
+ # Access worker_downloads through worker and device indices.
149
+ return self.worker_downloads[worker * self.devices + device]
150
+ else:
151
+ raise ValueError('Must specify either index, or worker and device.')
152
+
153
+ def get_current_batch_shards(self, worker: int, worker_sample_index: int,
154
+ sample_to_shard: Spanner) -> tuple[set, set]:
155
+ """Get this node's shards for the current batch.
156
+
157
+ Args:
158
+ worker (int): The worker.
159
+ worker_sample_index (int): The worker sample index.
160
+ sample_to_shard (Spanner): The mapping from samples to shards.
161
+
162
+ Returns:
163
+ Tuple[set, set]: shard ids needed by node, shard ids present in node.
164
+ """
165
+ if self.samples is not None:
166
+ batch_samples = remove_padded_samples(
167
+ self.samples[:, worker, worker_sample_index:worker_sample_index +
168
+ self.device_batch_size].flatten())
169
+ batch_shards = {sample_to_shard[sample] for sample in batch_samples}
170
+ shards_needed = batch_shards.difference(self.shards.keys())
171
+ shards_present = batch_shards.difference(shards_needed)
172
+ return shards_needed, shards_present
173
+ else:
174
+ raise AttributeError('Must set node samples before accessing them.')
175
+
176
+ def get_next_worker_with_downloads(self) -> Optional[OrderedSet]:
177
+ """Get the next worker with samples to download.
178
+
179
+ Returns:
180
+ Optional[OrderedSet]: The next worker's shard downloads, or None if no workers have
181
+ samples to download.
182
+ """
183
+ empty_download_counter = 0
184
+ worker_download = self.get_worker_download(index=self.worker_download_index)
185
+ while len(worker_download) == 0:
186
+ empty_download_counter += 1
187
+ self.increment_worker_download_index()
188
+ worker_download = self.get_worker_download(index=self.worker_download_index)
189
+
190
+ # No workers in the node have samples to download.
191
+ if empty_download_counter >= self.total_workers:
192
+ return None
193
+
194
+ return worker_download
195
+
196
+ def update_worker_predownloads(self, worker: int, worker_sample_index: int,
197
+ sample_to_shard: Spanner):
198
+ """Update the worker predownload samples for a worker and device.
199
+
200
+ Args:
201
+ worker (int): The current batch worker index.
202
+ worker_sample_index (int): The worker sample index.
203
+ sample_to_shard (Spanner): The mapping from samples to shards.
204
+ """
205
+ if self.samples is not None:
206
+ for device in range(self.devices):
207
+ # Retrieve new samples that are now within predownload range of the worker.
208
+ new_download_samples = remove_padded_samples(
209
+ self.samples[device, worker,
210
+ worker_sample_index + self.predownload:worker_sample_index +
211
+ self.device_batch_size + self.predownload])
212
+
213
+ # Want to maintain the shard access order.
214
+ new_download_shards = OrderedSet(
215
+ [sample_to_shard[sample] for sample in new_download_samples])
216
+
217
+ worker_downloads = self.get_worker_download(worker=worker, device=device)
218
+
219
+ # Add in new shards to the worker's shard downloads only if node does not yet have it.
220
+ for shard in new_download_shards:
221
+ if shard not in self.shards:
222
+ worker_downloads.add(shard)
223
+ else:
224
+ raise AttributeError('Must set node samples before accessing them.')