segment-everything 0.1.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 (145) hide show
  1. segment_everything/__init__.py +5 -0
  2. segment_everything/augmentation/albumentations_helper.py +0 -0
  3. segment_everything/detect_and_segment.py +131 -0
  4. segment_everything/napari_helper.py +15 -0
  5. segment_everything/prompt_generator.py +188 -0
  6. segment_everything/py.typed +5 -0
  7. segment_everything/stacked_label_dataset.py +113 -0
  8. segment_everything/stacked_labels.py +428 -0
  9. segment_everything/vendored/PromptGuidedDecoder/Prompt_guided_Mask_Decoder.pt +0 -0
  10. segment_everything/vendored/__init__.py +5 -0
  11. segment_everything/vendored/dice.py +158 -0
  12. segment_everything/vendored/efficientvit/__init__.py +0 -0
  13. segment_everything/vendored/efficientvit/apps/__init__.py +0 -0
  14. segment_everything/vendored/efficientvit/apps/data_provider/__init__.py +7 -0
  15. segment_everything/vendored/efficientvit/apps/data_provider/augment/__init__.py +6 -0
  16. segment_everything/vendored/efficientvit/apps/data_provider/augment/bbox.py +30 -0
  17. segment_everything/vendored/efficientvit/apps/data_provider/augment/color_aug.py +78 -0
  18. segment_everything/vendored/efficientvit/apps/data_provider/base.py +254 -0
  19. segment_everything/vendored/efficientvit/apps/data_provider/random_resolution/__init__.py +6 -0
  20. segment_everything/vendored/efficientvit/apps/data_provider/random_resolution/_data_loader.py +1538 -0
  21. segment_everything/vendored/efficientvit/apps/data_provider/random_resolution/_data_worker.py +357 -0
  22. segment_everything/vendored/efficientvit/apps/data_provider/random_resolution/controller.py +100 -0
  23. segment_everything/vendored/efficientvit/apps/setup.py +150 -0
  24. segment_everything/vendored/efficientvit/apps/trainer/__init__.py +6 -0
  25. segment_everything/vendored/efficientvit/apps/trainer/base.py +318 -0
  26. segment_everything/vendored/efficientvit/apps/trainer/run_config.py +129 -0
  27. segment_everything/vendored/efficientvit/apps/utils/__init__.py +12 -0
  28. segment_everything/vendored/efficientvit/apps/utils/dist.py +32 -0
  29. segment_everything/vendored/efficientvit/apps/utils/ema.py +52 -0
  30. segment_everything/vendored/efficientvit/apps/utils/export.py +45 -0
  31. segment_everything/vendored/efficientvit/apps/utils/init.py +66 -0
  32. segment_everything/vendored/efficientvit/apps/utils/lr.py +52 -0
  33. segment_everything/vendored/efficientvit/apps/utils/metric.py +43 -0
  34. segment_everything/vendored/efficientvit/apps/utils/misc.py +101 -0
  35. segment_everything/vendored/efficientvit/apps/utils/opt.py +28 -0
  36. segment_everything/vendored/efficientvit/cls_model_zoo.py +79 -0
  37. segment_everything/vendored/efficientvit/clscore/__init__.py +0 -0
  38. segment_everything/vendored/efficientvit/clscore/data_provider/__init__.py +5 -0
  39. segment_everything/vendored/efficientvit/clscore/data_provider/imagenet.py +142 -0
  40. segment_everything/vendored/efficientvit/clscore/trainer/__init__.py +6 -0
  41. segment_everything/vendored/efficientvit/clscore/trainer/cls_run_config.py +18 -0
  42. segment_everything/vendored/efficientvit/clscore/trainer/cls_trainer.py +265 -0
  43. segment_everything/vendored/efficientvit/clscore/trainer/utils/__init__.py +7 -0
  44. segment_everything/vendored/efficientvit/clscore/trainer/utils/label_smooth.py +18 -0
  45. segment_everything/vendored/efficientvit/clscore/trainer/utils/metric.py +23 -0
  46. segment_everything/vendored/efficientvit/clscore/trainer/utils/mixup.py +67 -0
  47. segment_everything/vendored/efficientvit/models/__init__.py +0 -0
  48. segment_everything/vendored/efficientvit/models/efficientvit/__init__.py +8 -0
  49. segment_everything/vendored/efficientvit/models/efficientvit/backbone.py +380 -0
  50. segment_everything/vendored/efficientvit/models/efficientvit/cls.py +188 -0
  51. segment_everything/vendored/efficientvit/models/efficientvit/sam.py +181 -0
  52. segment_everything/vendored/efficientvit/models/efficientvit/seg.py +373 -0
  53. segment_everything/vendored/efficientvit/models/nn/__init__.py +8 -0
  54. segment_everything/vendored/efficientvit/models/nn/act.py +30 -0
  55. segment_everything/vendored/efficientvit/models/nn/drop.py +104 -0
  56. segment_everything/vendored/efficientvit/models/nn/norm.py +164 -0
  57. segment_everything/vendored/efficientvit/models/nn/ops.py +597 -0
  58. segment_everything/vendored/efficientvit/models/utils/__init__.py +7 -0
  59. segment_everything/vendored/efficientvit/models/utils/list.py +53 -0
  60. segment_everything/vendored/efficientvit/models/utils/network.py +73 -0
  61. segment_everything/vendored/efficientvit/models/utils/random.py +65 -0
  62. segment_everything/vendored/efficientvit/sam_model_zoo.py +45 -0
  63. segment_everything/vendored/efficientvit/seg_model_zoo.py +70 -0
  64. segment_everything/vendored/get_object_aware.py +26 -0
  65. segment_everything/vendored/mobilesamv2/__init__.py +16 -0
  66. segment_everything/vendored/mobilesamv2/automatic_mask_generator.py +415 -0
  67. segment_everything/vendored/mobilesamv2/build_sam.py +246 -0
  68. segment_everything/vendored/mobilesamv2/modeling/__init__.py +11 -0
  69. segment_everything/vendored/mobilesamv2/modeling/common.py +43 -0
  70. segment_everything/vendored/mobilesamv2/modeling/image_encoder.py +394 -0
  71. segment_everything/vendored/mobilesamv2/modeling/mask_decoder.py +213 -0
  72. segment_everything/vendored/mobilesamv2/modeling/prompt_encoder.py +217 -0
  73. segment_everything/vendored/mobilesamv2/modeling/sam.py +203 -0
  74. segment_everything/vendored/mobilesamv2/modeling/transformer.py +240 -0
  75. segment_everything/vendored/mobilesamv2/predictor.py +384 -0
  76. segment_everything/vendored/mobilesamv2/utils/__init__.py +5 -0
  77. segment_everything/vendored/mobilesamv2/utils/amg.py +347 -0
  78. segment_everything/vendored/mobilesamv2/utils/onnx.py +144 -0
  79. segment_everything/vendored/mobilesamv2/utils/transforms.py +103 -0
  80. segment_everything/vendored/object_detection/__init__.py +0 -0
  81. segment_everything/vendored/object_detection/ultralytics/__init__.py +5 -0
  82. segment_everything/vendored/object_detection/ultralytics/nn/__init__.py +9 -0
  83. segment_everything/vendored/object_detection/ultralytics/nn/autobackend.py +658 -0
  84. segment_everything/vendored/object_detection/ultralytics/nn/autoshape.py +397 -0
  85. segment_everything/vendored/object_detection/ultralytics/nn/modules/__init__.py +110 -0
  86. segment_everything/vendored/object_detection/ultralytics/nn/modules/block.py +304 -0
  87. segment_everything/vendored/object_detection/ultralytics/nn/modules/conv.py +297 -0
  88. segment_everything/vendored/object_detection/ultralytics/nn/modules/head.py +468 -0
  89. segment_everything/vendored/object_detection/ultralytics/nn/modules/transformer.py +378 -0
  90. segment_everything/vendored/object_detection/ultralytics/nn/modules/utils.py +78 -0
  91. segment_everything/vendored/object_detection/ultralytics/nn/tasks.py +1049 -0
  92. segment_everything/vendored/object_detection/ultralytics/prompt_mobilesamv2/__init__.py +6 -0
  93. segment_everything/vendored/object_detection/ultralytics/prompt_mobilesamv2/model.py +104 -0
  94. segment_everything/vendored/object_detection/ultralytics/prompt_mobilesamv2/predict.py +95 -0
  95. segment_everything/vendored/object_detection/ultralytics/yolo/__init__.py +5 -0
  96. segment_everything/vendored/object_detection/ultralytics/yolo/cfg/__init__.py +588 -0
  97. segment_everything/vendored/object_detection/ultralytics/yolo/cfg/default.yaml +117 -0
  98. segment_everything/vendored/object_detection/ultralytics/yolo/data/__init__.py +9 -0
  99. segment_everything/vendored/object_detection/ultralytics/yolo/data/annotator.py +53 -0
  100. segment_everything/vendored/object_detection/ultralytics/yolo/data/augment.py +899 -0
  101. segment_everything/vendored/object_detection/ultralytics/yolo/data/base.py +286 -0
  102. segment_everything/vendored/object_detection/ultralytics/yolo/data/build.py +213 -0
  103. segment_everything/vendored/object_detection/ultralytics/yolo/data/converter.py +358 -0
  104. segment_everything/vendored/object_detection/ultralytics/yolo/data/dataloaders/__init__.py +0 -0
  105. segment_everything/vendored/object_detection/ultralytics/yolo/data/dataloaders/stream_loaders.py +459 -0
  106. segment_everything/vendored/object_detection/ultralytics/yolo/data/dataset.py +274 -0
  107. segment_everything/vendored/object_detection/ultralytics/yolo/data/dataset_wrappers.py +53 -0
  108. segment_everything/vendored/object_detection/ultralytics/yolo/data/scripts/download_weights.sh +18 -0
  109. segment_everything/vendored/object_detection/ultralytics/yolo/data/scripts/get_coco.sh +60 -0
  110. segment_everything/vendored/object_detection/ultralytics/yolo/data/scripts/get_coco128.sh +17 -0
  111. segment_everything/vendored/object_detection/ultralytics/yolo/data/scripts/get_imagenet.sh +51 -0
  112. segment_everything/vendored/object_detection/ultralytics/yolo/data/utils.py +716 -0
  113. segment_everything/vendored/object_detection/ultralytics/yolo/engine/__init__.py +0 -0
  114. segment_everything/vendored/object_detection/ultralytics/yolo/engine/exporter.py +1214 -0
  115. segment_everything/vendored/object_detection/ultralytics/yolo/engine/model.py +641 -0
  116. segment_everything/vendored/object_detection/ultralytics/yolo/engine/predictor.py +461 -0
  117. segment_everything/vendored/object_detection/ultralytics/yolo/engine/results.py +741 -0
  118. segment_everything/vendored/object_detection/ultralytics/yolo/utils/__init__.py +893 -0
  119. segment_everything/vendored/object_detection/ultralytics/yolo/utils/autobatch.py +108 -0
  120. segment_everything/vendored/object_detection/ultralytics/yolo/utils/callbacks/__init__.py +5 -0
  121. segment_everything/vendored/object_detection/ultralytics/yolo/utils/callbacks/base.py +212 -0
  122. segment_everything/vendored/object_detection/ultralytics/yolo/utils/checks.py +547 -0
  123. segment_everything/vendored/object_detection/ultralytics/yolo/utils/dist.py +67 -0
  124. segment_everything/vendored/object_detection/ultralytics/yolo/utils/downloads.py +353 -0
  125. segment_everything/vendored/object_detection/ultralytics/yolo/utils/errors.py +12 -0
  126. segment_everything/vendored/object_detection/ultralytics/yolo/utils/files.py +100 -0
  127. segment_everything/vendored/object_detection/ultralytics/yolo/utils/instance.py +391 -0
  128. segment_everything/vendored/object_detection/ultralytics/yolo/utils/loss.py +579 -0
  129. segment_everything/vendored/object_detection/ultralytics/yolo/utils/metrics.py +1189 -0
  130. segment_everything/vendored/object_detection/ultralytics/yolo/utils/ops.py +870 -0
  131. segment_everything/vendored/object_detection/ultralytics/yolo/utils/patches.py +45 -0
  132. segment_everything/vendored/object_detection/ultralytics/yolo/utils/plotting.py +767 -0
  133. segment_everything/vendored/object_detection/ultralytics/yolo/utils/tal.py +276 -0
  134. segment_everything/vendored/object_detection/ultralytics/yolo/utils/torch_utils.py +684 -0
  135. segment_everything/vendored/object_detection/ultralytics/yolo/utils/tuner.py +54 -0
  136. segment_everything/vendored/object_detection/ultralytics/yolo/v8/__init__.py +5 -0
  137. segment_everything/vendored/object_detection/ultralytics/yolo/v8/detect/__init__.py +5 -0
  138. segment_everything/vendored/object_detection/ultralytics/yolo/v8/detect/predict.py +69 -0
  139. segment_everything/vendored/tinyvit/__init__.py +2 -0
  140. segment_everything/vendored/tinyvit/tiny_vit.py +867 -0
  141. segment_everything/weights_helper.py +124 -0
  142. segment_everything-0.1.0.dist-info/METADATA +53 -0
  143. segment_everything-0.1.0.dist-info/RECORD +145 -0
  144. segment_everything-0.1.0.dist-info/WHEEL +4 -0
  145. segment_everything-0.1.0.dist-info/licenses/LICENSE +28 -0
@@ -0,0 +1,1538 @@
1
+ r"""This file is based on torch/utils/data/data_loader.py
2
+
3
+ Definition of the DataLoader and associated iterators that subclass _BaseDataLoaderIter
4
+
5
+ To support these two classes, in `./_utils` we define many utility methods and
6
+ functions to be run in multiprocessing. E.g., the data loading worker loop is
7
+ in `./_utils/worker.py`.
8
+ """
9
+
10
+ import functools
11
+ import itertools
12
+ import logging
13
+ import multiprocessing as python_multiprocessing
14
+ import os
15
+ import queue
16
+ import threading
17
+ import warnings
18
+ from typing import Any, Callable, Generic, Iterable, List, Optional, Sequence, TypeVar, Union
19
+
20
+ import torch
21
+ import torch.distributed as dist
22
+ import torch.multiprocessing as multiprocessing
23
+ import torch.utils.data.graph_settings
24
+ from torch._utils import ExceptionWrapper
25
+ from torch.utils.data import (
26
+ BatchSampler,
27
+ Dataset,
28
+ IterableDataset,
29
+ IterDataPipe,
30
+ MapDataPipe,
31
+ RandomSampler,
32
+ Sampler,
33
+ SequentialSampler,
34
+ _utils,
35
+ )
36
+ from torch.utils.data.datapipes.datapipe import _IterDataPipeSerializationWrapper, _MapDataPipeSerializationWrapper
37
+
38
+ from ._data_worker import _worker_loop
39
+
40
+ __all__ = ["RRSDataLoader"]
41
+
42
+ T_co = TypeVar("T_co", covariant=True)
43
+ T = TypeVar("T")
44
+ _worker_init_fn_t = Callable[[int], None]
45
+
46
+ # Ideally we would parameterize `DataLoader` by the return type of `collate_fn`, but there is currently no way to have that
47
+ # type parameter set to a default value if the user doesn't pass in a custom 'collate_fn'.
48
+ # See https://github.com/python/mypy/issues/3737.
49
+ _collate_fn_t = Callable[[List[T]], Any]
50
+
51
+
52
+ # These functions used to be defined in this file. However, it was moved to
53
+ # _utils/collate.py. Although it is rather hard to access this from user land
54
+ # (one has to explicitly directly `import torch.utils.data.dataloader`), there
55
+ # probably is user code out there using it. This aliasing maintains BC in this
56
+ # aspect.
57
+ default_collate: _collate_fn_t = _utils.collate.default_collate
58
+ default_convert = _utils.collate.default_convert
59
+
60
+ get_worker_info = _utils.worker.get_worker_info
61
+
62
+ logger = logging.getLogger(__name__)
63
+
64
+
65
+ class _DatasetKind:
66
+ Map = 0
67
+ Iterable = 1
68
+
69
+ @staticmethod
70
+ def create_fetcher(kind, dataset, auto_collation, collate_fn, drop_last):
71
+ if kind == _DatasetKind.Map:
72
+ return _utils.fetch._MapDatasetFetcher(dataset, auto_collation, collate_fn, drop_last)
73
+ else:
74
+ return _utils.fetch._IterableDatasetFetcher(dataset, auto_collation, collate_fn, drop_last)
75
+
76
+
77
+ class _InfiniteConstantSampler(Sampler):
78
+ r"""Analogous to ``itertools.repeat(None, None)``.
79
+ Used as sampler for :class:`~torch.utils.data.IterableDataset`.
80
+
81
+ Args:
82
+ data_source (Dataset): dataset to sample from
83
+ """
84
+
85
+ def __init__(self):
86
+ super().__init__(None)
87
+
88
+ def __iter__(self):
89
+ while True:
90
+ yield None
91
+
92
+
93
+ def _get_distributed_settings():
94
+ if dist.is_available() and dist.is_initialized():
95
+ return dist.get_world_size(), dist.get_rank()
96
+ else:
97
+ return 1, 0
98
+
99
+
100
+ def _sharding_worker_init_fn(worker_init_fn, world_size, rank_id, worker_id):
101
+ global_worker_id = worker_id
102
+ info = torch.utils.data.get_worker_info()
103
+ assert info is not None
104
+ total_workers = info.num_workers
105
+ datapipe = info.dataset
106
+ assert isinstance(datapipe, (IterDataPipe, MapDataPipe))
107
+ # To distribute elements across distributed process evenly, we should shard data on distributed
108
+ # processes first then shard on worker processes
109
+ total_workers *= world_size
110
+ global_worker_id = global_worker_id * world_size + rank_id
111
+ # For BC, use default SHARDING_PRIORITIES
112
+ torch.utils.data.graph_settings.apply_sharding(datapipe, total_workers, global_worker_id)
113
+ if worker_init_fn is not None:
114
+ worker_init_fn(worker_id)
115
+
116
+
117
+ def _share_dist_seed(generator, pg):
118
+ _shared_seed = torch.empty((), dtype=torch.int64).random_(generator=generator)
119
+ if isinstance(pg, dist.ProcessGroup):
120
+ dist.broadcast(_shared_seed, src=0, group=pg)
121
+ return _shared_seed.item()
122
+
123
+
124
+ class RRSDataLoader(Generic[T_co]):
125
+ r"""
126
+ Data loader. Combines a dataset and a sampler, and provides an iterable over
127
+ the given dataset.
128
+
129
+ The :class:`~torch.utils.data.DataLoader` supports both map-style and
130
+ iterable-style datasets with single- or multi-process loading, customizing
131
+ loading order and optional automatic batching (collation) and memory pinning.
132
+
133
+ See :py:mod:`torch.utils.data` documentation page for more details.
134
+
135
+ Args:
136
+ dataset (Dataset): dataset from which to load the data.
137
+ batch_size (int, optional): how many samples per batch to load
138
+ (default: ``1``).
139
+ shuffle (bool, optional): set to ``True`` to have the data reshuffled
140
+ at every epoch (default: ``False``).
141
+ sampler (Sampler or Iterable, optional): defines the strategy to draw
142
+ samples from the dataset. Can be any ``Iterable`` with ``__len__``
143
+ implemented. If specified, :attr:`shuffle` must not be specified.
144
+ batch_sampler (Sampler or Iterable, optional): like :attr:`sampler`, but
145
+ returns a batch of indices at a time. Mutually exclusive with
146
+ :attr:`batch_size`, :attr:`shuffle`, :attr:`sampler`,
147
+ and :attr:`drop_last`.
148
+ num_workers (int, optional): how many subprocesses to use for data
149
+ loading. ``0`` means that the data will be loaded in the main process.
150
+ (default: ``0``)
151
+ collate_fn (Callable, optional): merges a list of samples to form a
152
+ mini-batch of Tensor(s). Used when using batched loading from a
153
+ map-style dataset.
154
+ pin_memory (bool, optional): If ``True``, the data loader will copy Tensors
155
+ into device/CUDA pinned memory before returning them. If your data elements
156
+ are a custom type, or your :attr:`collate_fn` returns a batch that is a custom type,
157
+ see the example below.
158
+ drop_last (bool, optional): set to ``True`` to drop the last incomplete batch,
159
+ if the dataset size is not divisible by the batch size. If ``False`` and
160
+ the size of dataset is not divisible by the batch size, then the last batch
161
+ will be smaller. (default: ``False``)
162
+ timeout (numeric, optional): if positive, the timeout value for collecting a batch
163
+ from workers. Should always be non-negative. (default: ``0``)
164
+ worker_init_fn (Callable, optional): If not ``None``, this will be called on each
165
+ worker subprocess with the worker id (an int in ``[0, num_workers - 1]``) as
166
+ input, after seeding and before data loading. (default: ``None``)
167
+ generator (torch.Generator, optional): If not ``None``, this RNG will be used
168
+ by RandomSampler to generate random indexes and multiprocessing to generate
169
+ `base_seed` for workers. (default: ``None``)
170
+ prefetch_factor (int, optional, keyword-only arg): Number of batches loaded
171
+ in advance by each worker. ``2`` means there will be a total of
172
+ 2 * num_workers batches prefetched across all workers. (default value depends
173
+ on the set value for num_workers. If value of num_workers=0 default is ``None``.
174
+ Otherwise if value of num_workers>0 default is ``2``).
175
+ persistent_workers (bool, optional): If ``True``, the data loader will not shutdown
176
+ the worker processes after a dataset has been consumed once. This allows to
177
+ maintain the workers `Dataset` instances alive. (default: ``False``)
178
+ pin_memory_device (str, optional): the data loader will copy Tensors
179
+ into device pinned memory before returning them if pin_memory is set to true.
180
+
181
+
182
+ .. warning:: If the ``spawn`` start method is used, :attr:`worker_init_fn`
183
+ cannot be an unpicklable object, e.g., a lambda function. See
184
+ :ref:`multiprocessing-best-practices` on more details related
185
+ to multiprocessing in PyTorch.
186
+
187
+ .. warning:: ``len(dataloader)`` heuristic is based on the length of the sampler used.
188
+ When :attr:`dataset` is an :class:`~torch.utils.data.IterableDataset`,
189
+ it instead returns an estimate based on ``len(dataset) / batch_size``, with proper
190
+ rounding depending on :attr:`drop_last`, regardless of multi-process loading
191
+ configurations. This represents the best guess PyTorch can make because PyTorch
192
+ trusts user :attr:`dataset` code in correctly handling multi-process
193
+ loading to avoid duplicate data.
194
+
195
+ However, if sharding results in multiple workers having incomplete last batches,
196
+ this estimate can still be inaccurate, because (1) an otherwise complete batch can
197
+ be broken into multiple ones and (2) more than one batch worth of samples can be
198
+ dropped when :attr:`drop_last` is set. Unfortunately, PyTorch can not detect such
199
+ cases in general.
200
+
201
+ See `Dataset Types`_ for more details on these two types of datasets and how
202
+ :class:`~torch.utils.data.IterableDataset` interacts with
203
+ `Multi-process data loading`_.
204
+
205
+ .. warning:: See :ref:`reproducibility`, and :ref:`dataloader-workers-random-seed`, and
206
+ :ref:`data-loading-randomness` notes for random seed related questions.
207
+ """
208
+ dataset: Dataset[T_co]
209
+ batch_size: Optional[int]
210
+ num_workers: int
211
+ pin_memory: bool
212
+ drop_last: bool
213
+ timeout: float
214
+ sampler: Union[Sampler, Iterable]
215
+ pin_memory_device: str
216
+ prefetch_factor: Optional[int]
217
+ _iterator: Optional["_BaseDataLoaderIter"]
218
+ __initialized = False
219
+
220
+ def __init__(
221
+ self,
222
+ dataset: Dataset[T_co],
223
+ batch_size: Optional[int] = 1,
224
+ shuffle: Optional[bool] = None,
225
+ sampler: Union[Sampler, Iterable, None] = None,
226
+ batch_sampler: Union[Sampler[Sequence], Iterable[Sequence], None] = None,
227
+ num_workers: int = 0,
228
+ collate_fn: Optional[_collate_fn_t] = None,
229
+ pin_memory: bool = False,
230
+ drop_last: bool = False,
231
+ timeout: float = 0,
232
+ worker_init_fn: Optional[_worker_init_fn_t] = None,
233
+ multiprocessing_context=None,
234
+ generator=None,
235
+ *,
236
+ prefetch_factor: Optional[int] = None,
237
+ persistent_workers: bool = False,
238
+ pin_memory_device: str = ""
239
+ ):
240
+ torch._C._log_api_usage_once("python.data_loader")
241
+
242
+ if num_workers < 0:
243
+ raise ValueError(
244
+ "num_workers option should be non-negative; " "use num_workers=0 to disable multiprocessing."
245
+ )
246
+
247
+ if timeout < 0:
248
+ raise ValueError("timeout option should be non-negative")
249
+
250
+ if num_workers == 0 and prefetch_factor is not None:
251
+ raise ValueError(
252
+ "prefetch_factor option could only be specified in multiprocessing."
253
+ "let num_workers > 0 to enable multiprocessing, otherwise set prefetch_factor to None."
254
+ )
255
+ elif num_workers > 0 and prefetch_factor is None:
256
+ prefetch_factor = 2
257
+ elif prefetch_factor is not None and prefetch_factor < 0:
258
+ raise ValueError("prefetch_factor option should be non-negative")
259
+
260
+ if persistent_workers and num_workers == 0:
261
+ raise ValueError("persistent_workers option needs num_workers > 0")
262
+
263
+ self.dataset = dataset
264
+ self.num_workers = num_workers
265
+ self.prefetch_factor = prefetch_factor
266
+ self.pin_memory = pin_memory
267
+ self.pin_memory_device = pin_memory_device
268
+ self.timeout = timeout
269
+ self.worker_init_fn = worker_init_fn
270
+ self.multiprocessing_context = multiprocessing_context
271
+
272
+ # Adds forward compatibilities so classic DataLoader can work with DataPipes:
273
+ # _DataPipeSerializationWrapper container makes it easier to serialize without redefining pickler
274
+ if isinstance(self.dataset, IterDataPipe):
275
+ self.dataset = _IterDataPipeSerializationWrapper(self.dataset)
276
+ elif isinstance(self.dataset, MapDataPipe):
277
+ self.dataset = _MapDataPipeSerializationWrapper(self.dataset)
278
+
279
+ # Arg-check dataset related before checking samplers because we want to
280
+ # tell users that iterable-style datasets are incompatible with custom
281
+ # samplers first, so that they don't learn that this combo doesn't work
282
+ # after spending time fixing the custom sampler errors.
283
+ if isinstance(dataset, IterableDataset):
284
+ self._dataset_kind = _DatasetKind.Iterable
285
+ # NOTE [ Custom Samplers and IterableDataset ]
286
+ #
287
+ # `IterableDataset` does not support custom `batch_sampler` or
288
+ # `sampler` since the key is irrelevant (unless we support
289
+ # generator-style dataset one day...).
290
+ #
291
+ # For `sampler`, we always create a dummy sampler. This is an
292
+ # infinite sampler even when the dataset may have an implemented
293
+ # finite `__len__` because in multi-process data loading, naive
294
+ # settings will return duplicated data (which may be desired), and
295
+ # thus using a sampler with length matching that of dataset will
296
+ # cause data lost (you may have duplicates of the first couple
297
+ # batches, but never see anything afterwards). Therefore,
298
+ # `Iterabledataset` always uses an infinite sampler, an instance of
299
+ # `_InfiniteConstantSampler` defined above.
300
+ #
301
+ # A custom `batch_sampler` essentially only controls the batch size.
302
+ # However, it is unclear how useful it would be since an iterable-style
303
+ # dataset can handle that within itself. Moreover, it is pointless
304
+ # in multi-process data loading as the assignment order of batches
305
+ # to workers is an implementation detail so users can not control
306
+ # how to batchify each worker's iterable. Thus, we disable this
307
+ # option. If this turns out to be useful in future, we can re-enable
308
+ # this, and support custom samplers that specify the assignments to
309
+ # specific workers.
310
+ if isinstance(dataset, IterDataPipe):
311
+ if shuffle is not None:
312
+ dataset = torch.utils.data.graph_settings.apply_shuffle_settings(dataset, shuffle=shuffle)
313
+ # We cannot check `shuffle is not None` here, since previously `shuffle=False` was the default.
314
+ elif shuffle not in {False, None}:
315
+ raise ValueError(
316
+ "DataLoader with IterableDataset: expected unspecified "
317
+ "shuffle option, but got shuffle={}".format(shuffle)
318
+ )
319
+
320
+ if sampler is not None:
321
+ # See NOTE [ Custom Samplers and IterableDataset ]
322
+ raise ValueError(
323
+ "DataLoader with IterableDataset: expected unspecified "
324
+ "sampler option, but got sampler={}".format(sampler)
325
+ )
326
+ elif batch_sampler is not None:
327
+ # See NOTE [ Custom Samplers and IterableDataset ]
328
+ raise ValueError(
329
+ "DataLoader with IterableDataset: expected unspecified "
330
+ "batch_sampler option, but got batch_sampler={}".format(batch_sampler)
331
+ )
332
+ else:
333
+ shuffle = bool(shuffle)
334
+ self._dataset_kind = _DatasetKind.Map
335
+
336
+ if sampler is not None and shuffle:
337
+ raise ValueError("sampler option is mutually exclusive with " "shuffle")
338
+
339
+ if batch_sampler is not None:
340
+ # auto_collation with custom batch_sampler
341
+ if batch_size != 1 or shuffle or sampler is not None or drop_last:
342
+ raise ValueError(
343
+ "batch_sampler option is mutually exclusive " "with batch_size, shuffle, sampler, and " "drop_last"
344
+ )
345
+ batch_size = None
346
+ drop_last = False
347
+ elif batch_size is None:
348
+ # no auto_collation
349
+ if drop_last:
350
+ raise ValueError(
351
+ "batch_size=None option disables auto-batching " "and is mutually exclusive with drop_last"
352
+ )
353
+
354
+ if sampler is None: # give default samplers
355
+ if self._dataset_kind == _DatasetKind.Iterable:
356
+ # See NOTE [ Custom Samplers and IterableDataset ]
357
+ sampler = _InfiniteConstantSampler()
358
+ else: # map-style
359
+ if shuffle:
360
+ sampler = RandomSampler(dataset, generator=generator) # type: ignore[arg-type]
361
+ else:
362
+ sampler = SequentialSampler(dataset) # type: ignore[arg-type]
363
+
364
+ if batch_size is not None and batch_sampler is None:
365
+ # auto_collation without custom batch_sampler
366
+ batch_sampler = BatchSampler(sampler, batch_size, drop_last)
367
+
368
+ self.batch_size = batch_size
369
+ self.drop_last = drop_last
370
+ self.sampler = sampler
371
+ self.batch_sampler = batch_sampler
372
+ self.generator = generator
373
+
374
+ if collate_fn is None:
375
+ if self._auto_collation:
376
+ collate_fn = _utils.collate.default_collate
377
+ else:
378
+ collate_fn = _utils.collate.default_convert
379
+
380
+ self.collate_fn = collate_fn
381
+ self.persistent_workers = persistent_workers
382
+
383
+ self.__initialized = True
384
+ self._IterableDataset_len_called = None # See NOTE [ IterableDataset and __len__ ]
385
+
386
+ self._iterator = None
387
+
388
+ self.check_worker_number_rationality()
389
+
390
+ torch.set_vital("Dataloader", "enabled", "True") # type: ignore[attr-defined]
391
+
392
+ def _get_iterator(self) -> "_BaseDataLoaderIter":
393
+ if self.num_workers == 0:
394
+ return _SingleProcessDataLoaderIter(self)
395
+ else:
396
+ self.check_worker_number_rationality()
397
+ return _MultiProcessingDataLoaderIter(self)
398
+
399
+ @property
400
+ def multiprocessing_context(self):
401
+ return self.__multiprocessing_context
402
+
403
+ @multiprocessing_context.setter
404
+ def multiprocessing_context(self, multiprocessing_context):
405
+ if multiprocessing_context is not None:
406
+ if self.num_workers > 0:
407
+ if isinstance(multiprocessing_context, str):
408
+ valid_start_methods = multiprocessing.get_all_start_methods()
409
+ if multiprocessing_context not in valid_start_methods:
410
+ raise ValueError(
411
+ (
412
+ "multiprocessing_context option "
413
+ "should specify a valid start method in {!r}, but got "
414
+ "multiprocessing_context={!r}"
415
+ ).format(valid_start_methods, multiprocessing_context)
416
+ )
417
+ multiprocessing_context = multiprocessing.get_context(multiprocessing_context)
418
+
419
+ if not isinstance(multiprocessing_context, python_multiprocessing.context.BaseContext):
420
+ raise TypeError(
421
+ (
422
+ "multiprocessing_context option should be a valid context "
423
+ "object or a string specifying the start method, but got "
424
+ "multiprocessing_context={}"
425
+ ).format(multiprocessing_context)
426
+ )
427
+ else:
428
+ raise ValueError(
429
+ (
430
+ "multiprocessing_context can only be used with "
431
+ "multi-process loading (num_workers > 0), but got "
432
+ "num_workers={}"
433
+ ).format(self.num_workers)
434
+ )
435
+
436
+ self.__multiprocessing_context = multiprocessing_context
437
+
438
+ def __setattr__(self, attr, val):
439
+ if self.__initialized and attr in (
440
+ "batch_size",
441
+ "batch_sampler",
442
+ "sampler",
443
+ "drop_last",
444
+ "dataset",
445
+ "persistent_workers",
446
+ ):
447
+ raise ValueError(
448
+ "{} attribute should not be set after {} is " "initialized".format(attr, self.__class__.__name__)
449
+ )
450
+
451
+ super().__setattr__(attr, val)
452
+
453
+ # We quote '_BaseDataLoaderIter' since it isn't defined yet and the definition can't be moved up
454
+ # since '_BaseDataLoaderIter' references 'DataLoader'.
455
+ def __iter__(self) -> "_BaseDataLoaderIter":
456
+ # When using a single worker the returned iterator should be
457
+ # created everytime to avoid reseting its state
458
+ # However, in the case of a multiple workers iterator
459
+ # the iterator is only created once in the lifetime of the
460
+ # DataLoader object so that workers can be reused
461
+ if self.persistent_workers and self.num_workers > 0:
462
+ if self._iterator is None:
463
+ self._iterator = self._get_iterator()
464
+ else:
465
+ self._iterator._reset(self)
466
+ return self._iterator
467
+ else:
468
+ return self._get_iterator()
469
+
470
+ @property
471
+ def _auto_collation(self):
472
+ return self.batch_sampler is not None
473
+
474
+ @property
475
+ def _index_sampler(self):
476
+ # The actual sampler used for generating indices for `_DatasetFetcher`
477
+ # (see _utils/fetch.py) to read data at each time. This would be
478
+ # `.batch_sampler` if in auto-collation mode, and `.sampler` otherwise.
479
+ # We can't change `.sampler` and `.batch_sampler` attributes for BC
480
+ # reasons.
481
+ if self._auto_collation:
482
+ return self.batch_sampler
483
+ else:
484
+ return self.sampler
485
+
486
+ def __len__(self) -> int:
487
+ if self._dataset_kind == _DatasetKind.Iterable:
488
+ # NOTE [ IterableDataset and __len__ ]
489
+ #
490
+ # For `IterableDataset`, `__len__` could be inaccurate when one naively
491
+ # does multi-processing data loading, since the samples will be duplicated.
492
+ # However, no real use case should be actually using that behavior, so
493
+ # it should count as a user error. We should generally trust user
494
+ # code to do the proper thing (e.g., configure each replica differently
495
+ # in `__iter__`), and give us the correct `__len__` if they choose to
496
+ # implement it (this will still throw if the dataset does not implement
497
+ # a `__len__`).
498
+ #
499
+ # To provide a further warning, we track if `__len__` was called on the
500
+ # `DataLoader`, save the returned value in `self._len_called`, and warn
501
+ # if the iterator ends up yielding more than this number of samples.
502
+
503
+ # Cannot statically verify that dataset is Sized
504
+ length = self._IterableDataset_len_called = len(self.dataset) # type: ignore[assignment, arg-type]
505
+ if self.batch_size is not None: # IterableDataset doesn't allow custom sampler or batch_sampler
506
+ from math import ceil
507
+
508
+ if self.drop_last:
509
+ length = length // self.batch_size
510
+ else:
511
+ length = ceil(length / self.batch_size)
512
+ return length
513
+ else:
514
+ return len(self._index_sampler)
515
+
516
+ def check_worker_number_rationality(self):
517
+ # This function check whether the dataloader's worker number is rational based on
518
+ # current system's resource. Current rule is that if the number of workers this
519
+ # Dataloader will create is bigger than the number of logical cpus that is allowed to
520
+ # use, than we will pop up a warning to let user pay attention.
521
+ #
522
+ # eg. If current system has 2 physical CPUs with 16 cores each. And each core support 2
523
+ # threads, then the total logical cpus here is 2 * 16 * 2 = 64. Let's say current
524
+ # DataLoader process can use half of them which is 32, then the rational max number of
525
+ # worker that initiated from this process is 32.
526
+ # Now, let's say the created DataLoader has num_works = 40, which is bigger than 32.
527
+ # So the warning message is triggered to notify the user to lower the worker number if
528
+ # necessary.
529
+ #
530
+ #
531
+ # [Note] Please note that this function repects `cpuset` only when os.sched_getaffinity is
532
+ # available (available in most of Linux system, but not OSX and Windows).
533
+ # When os.sched_getaffinity is not available, os.cpu_count() is called instead, but
534
+ # it doesn't repect cpuset.
535
+ # We don't take threading into account since each worker process is single threaded
536
+ # at this time.
537
+ #
538
+ # We don't set any threading flags (eg. OMP_NUM_THREADS, MKL_NUM_THREADS, etc)
539
+ # other than `torch.set_num_threads` to 1 in the worker process, if the passing
540
+ # in functions use 3rd party modules that rely on those threading flags to determine
541
+ # how many thread to create (eg. numpy, etc), then it is caller's responsibility to
542
+ # set those flags correctly.
543
+ def _create_warning_msg(num_worker_suggest, num_worker_created, cpuset_checked):
544
+
545
+ suggested_max_worker_msg = (
546
+ (
547
+ (
548
+ "Our suggested max number of worker in current system is {}{}, which is smaller "
549
+ "than what this DataLoader is going to create."
550
+ ).format(
551
+ num_worker_suggest,
552
+ ("" if cpuset_checked else " (`cpuset` is not taken into account)"),
553
+ )
554
+ )
555
+ if num_worker_suggest is not None
556
+ else ("DataLoader is not able to compute a suggested max number of worker in current system.")
557
+ )
558
+
559
+ warn_msg = (
560
+ "This DataLoader will create {} worker processes in total. {} "
561
+ "Please be aware that excessive worker creation might get DataLoader running slow or even freeze, "
562
+ "lower the worker number to avoid potential slowness/freeze if necessary."
563
+ ).format(num_worker_created, suggested_max_worker_msg)
564
+ return warn_msg
565
+
566
+ if not self.num_workers or self.num_workers == 0:
567
+ return
568
+
569
+ # try to compute a suggested max number of worker based on system's resource
570
+ max_num_worker_suggest = None
571
+ cpuset_checked = False
572
+ if hasattr(os, "sched_getaffinity"):
573
+ try:
574
+ max_num_worker_suggest = len(os.sched_getaffinity(0))
575
+ cpuset_checked = True
576
+ except Exception:
577
+ pass
578
+ if max_num_worker_suggest is None:
579
+ # os.cpu_count() could return Optional[int]
580
+ # get cpu count first and check None in order to satify mypy check
581
+ cpu_count = os.cpu_count()
582
+ if cpu_count is not None:
583
+ max_num_worker_suggest = cpu_count
584
+
585
+ if max_num_worker_suggest is None:
586
+ warnings.warn(_create_warning_msg(max_num_worker_suggest, self.num_workers, cpuset_checked))
587
+ return
588
+
589
+ if self.num_workers > max_num_worker_suggest:
590
+ warnings.warn(_create_warning_msg(max_num_worker_suggest, self.num_workers, cpuset_checked))
591
+
592
+
593
+ class _BaseDataLoaderIter:
594
+ def __init__(self, loader: RRSDataLoader) -> None:
595
+ self._dataset = loader.dataset
596
+ self._shared_seed = None
597
+ self._pg = None
598
+ if isinstance(self._dataset, IterDataPipe):
599
+ if dist.is_available() and dist.is_initialized():
600
+ self._pg = dist.new_group(backend="gloo")
601
+ self._shared_seed = _share_dist_seed(loader.generator, self._pg)
602
+ shared_rng = torch.Generator()
603
+ shared_rng.manual_seed(self._shared_seed)
604
+ self._dataset = torch.utils.data.graph_settings.apply_random_seed(self._dataset, shared_rng)
605
+ self._dataset_kind = loader._dataset_kind
606
+ self._IterableDataset_len_called = loader._IterableDataset_len_called
607
+ self._auto_collation = loader._auto_collation
608
+ self._drop_last = loader.drop_last
609
+ self._index_sampler = loader._index_sampler
610
+ self._num_workers = loader.num_workers
611
+ ws, rank = _get_distributed_settings()
612
+ self._world_size = ws
613
+ self._rank = rank
614
+ # for other backends, pin_memory_device need to set. if not set
615
+ # default behaviour is CUDA device. if pin_memory_device is selected
616
+ # and pin_memory is not set, the default behaviour false.
617
+ if len(loader.pin_memory_device) == 0:
618
+ self._pin_memory = loader.pin_memory and torch.cuda.is_available()
619
+ self._pin_memory_device = None
620
+ else:
621
+ if not loader.pin_memory:
622
+ warn_msg = (
623
+ "pin memory device is set and pin_memory flag is not used then device pinned memory won't be used"
624
+ "please set pin_memory to true, if you need to use the device pin memory"
625
+ )
626
+ warnings.warn(warn_msg)
627
+
628
+ self._pin_memory = loader.pin_memory
629
+ self._pin_memory_device = loader.pin_memory_device
630
+ self._timeout = loader.timeout
631
+ self._collate_fn = loader.collate_fn
632
+ self._sampler_iter = iter(self._index_sampler)
633
+ self._base_seed = torch.empty((), dtype=torch.int64).random_(generator=loader.generator).item()
634
+ self._persistent_workers = loader.persistent_workers
635
+ self._num_yielded = 0
636
+ self._profile_name = "enumerate(DataLoader)#{}.__next__".format(self.__class__.__name__)
637
+
638
+ def __iter__(self) -> "_BaseDataLoaderIter":
639
+ return self
640
+
641
+ def _reset(self, loader, first_iter=False):
642
+ self._sampler_iter = iter(self._index_sampler)
643
+ self._num_yielded = 0
644
+ self._IterableDataset_len_called = loader._IterableDataset_len_called
645
+ if isinstance(self._dataset, IterDataPipe):
646
+ self._shared_seed = _share_dist_seed(loader.generator, self._pg)
647
+ shared_rng = torch.Generator()
648
+ shared_rng.manual_seed(self._shared_seed)
649
+ self._dataset = torch.utils.data.graph_settings.apply_random_seed(self._dataset, shared_rng)
650
+
651
+ def _next_index(self):
652
+ return next(self._sampler_iter) # may raise StopIteration
653
+
654
+ def _next_data(self):
655
+ raise NotImplementedError
656
+
657
+ def __next__(self) -> Any:
658
+ with torch.autograd.profiler.record_function(self._profile_name):
659
+ if self._sampler_iter is None:
660
+ # TODO(https://github.com/pytorch/pytorch/issues/76750)
661
+ self._reset() # type: ignore[call-arg]
662
+ data = self._next_data()
663
+ self._num_yielded += 1
664
+ if (
665
+ self._dataset_kind == _DatasetKind.Iterable
666
+ and self._IterableDataset_len_called is not None
667
+ and self._num_yielded > self._IterableDataset_len_called
668
+ ):
669
+ warn_msg = (
670
+ "Length of IterableDataset {} was reported to be {} (when accessing len(dataloader)), but {} "
671
+ "samples have been fetched. "
672
+ ).format(self._dataset, self._IterableDataset_len_called, self._num_yielded)
673
+ if self._num_workers > 0:
674
+ warn_msg += (
675
+ "For multiprocessing data-loading, this could be caused by not properly configuring the "
676
+ "IterableDataset replica at each worker. Please see "
677
+ "https://pytorch.org/docs/stable/data.html#torch.utils.data.IterableDataset for examples."
678
+ )
679
+ warnings.warn(warn_msg)
680
+ return data
681
+
682
+ def __len__(self) -> int:
683
+ return len(self._index_sampler)
684
+
685
+ def __getstate__(self):
686
+ # TODO: add limited pickling support for sharing an iterator
687
+ # across multiple threads for HOGWILD.
688
+ # Probably the best way to do this is by moving the sample pushing
689
+ # to a separate thread and then just sharing the data queue
690
+ # but signalling the end is tricky without a non-blocking API
691
+ raise NotImplementedError("{} cannot be pickled", self.__class__.__name__)
692
+
693
+
694
+ class _SingleProcessDataLoaderIter(_BaseDataLoaderIter):
695
+ def __init__(self, loader):
696
+ super().__init__(loader)
697
+ assert self._timeout == 0
698
+ assert self._num_workers == 0
699
+
700
+ # Adds forward compatibilities so classic DataLoader can work with DataPipes:
701
+ # Taking care of distributed sharding
702
+ if isinstance(self._dataset, (IterDataPipe, MapDataPipe)):
703
+ # For BC, use default SHARDING_PRIORITIES
704
+ torch.utils.data.graph_settings.apply_sharding(self._dataset, self._world_size, self._rank)
705
+
706
+ self._dataset_fetcher = _DatasetKind.create_fetcher(
707
+ self._dataset_kind,
708
+ self._dataset,
709
+ self._auto_collation,
710
+ self._collate_fn,
711
+ self._drop_last,
712
+ )
713
+
714
+ def _next_data(self):
715
+ index = self._next_index() # may raise StopIteration
716
+ data = self._dataset_fetcher.fetch(index) # may raise StopIteration
717
+ if self._pin_memory:
718
+ data = _utils.pin_memory.pin_memory(data, self._pin_memory_device)
719
+ return data
720
+
721
+
722
+ class _MultiProcessingDataLoaderIter(_BaseDataLoaderIter):
723
+ r"""Iterates once over the DataLoader's dataset, as specified by the sampler"""
724
+
725
+ # NOTE [ Data Loader Multiprocessing Shutdown Logic ]
726
+ #
727
+ # Preliminary:
728
+ #
729
+ # Our data model looks like this (queues are indicated with curly brackets):
730
+ #
731
+ # main process ||
732
+ # | ||
733
+ # {index_queue} ||
734
+ # | ||
735
+ # worker processes || DATA
736
+ # | ||
737
+ # {worker_result_queue} || FLOW
738
+ # | ||
739
+ # pin_memory_thread of main process || DIRECTION
740
+ # | ||
741
+ # {data_queue} ||
742
+ # | ||
743
+ # data output \/
744
+ #
745
+ # P.S. `worker_result_queue` and `pin_memory_thread` part may be omitted if
746
+ # `pin_memory=False`.
747
+ #
748
+ #
749
+ # Terminating multiprocessing logic requires very careful design. In
750
+ # particular, we need to make sure that
751
+ #
752
+ # 1. The iterator gracefully exits the workers when its last reference is
753
+ # gone or it is depleted.
754
+ #
755
+ # In this case, the workers should be gracefully exited because the
756
+ # main process may still need to continue to run, and we want cleaning
757
+ # up code in the workers to be executed (e.g., releasing GPU memory).
758
+ # Naturally, we implement the shutdown logic in `__del__` of
759
+ # DataLoaderIterator.
760
+ #
761
+ # We delay the discussion on the logic in this case until later.
762
+ #
763
+ # 2. The iterator exits the workers when the loader process and/or worker
764
+ # processes exits normally or with error.
765
+ #
766
+ # We set all workers and `pin_memory_thread` to have `daemon=True`.
767
+ #
768
+ # You may ask, why can't we make the workers non-daemonic, and
769
+ # gracefully exit using the same logic as we have in `__del__` when the
770
+ # iterator gets deleted (see 1 above)?
771
+ #
772
+ # First of all, `__del__` is **not** guaranteed to be called when
773
+ # interpreter exits. Even if it is called, by the time it executes,
774
+ # many Python core library resources may alreay be freed, and even
775
+ # simple things like acquiring an internal lock of a queue may hang.
776
+ # Therefore, in this case, we actually need to prevent `__del__` from
777
+ # being executed, and rely on the automatic termination of daemonic
778
+ # children.
779
+ #
780
+ # Thus, we register an `atexit` hook that sets a global flag
781
+ # `_utils.python_exit_status`. Since `atexit` hooks are executed in the
782
+ # reverse order of registration, we are guaranteed that this flag is
783
+ # set before library resources we use are freed (which, at least in
784
+ # CPython, is done via an `atexit` handler defined in
785
+ # `multiprocessing/util.py`
786
+ # https://github.com/python/cpython/blob/c606624af8d4cb3b4a052fb263bb983b3f87585b/Lib/multiprocessing/util.py#L320-L362
787
+ # registered when an object requiring this mechanism is first
788
+ # created, e.g., `mp.Queue`
789
+ # https://github.com/python/cpython/blob/c606624af8d4cb3b4a052fb263bb983b3f87585b/Lib/multiprocessing/context.py#L100-L103
790
+ # https://github.com/python/cpython/blob/c606624af8d4cb3b4a052fb263bb983b3f87585b/Lib/multiprocessing/queues.py#L29
791
+ # )
792
+ #
793
+ # So in `__del__`, we check if `_utils.python_exit_status` is set or
794
+ # `None` (freed), and perform no-op if so.
795
+ #
796
+ # However, simply letting library clean-up codes run can also be bad,
797
+ # because such codes (i.e., `multiprocessing.util._exit_function()`)
798
+ # include join putting threads for `mp.Queue`, which can be blocking.
799
+ # Hence, the main process putting threads are called with
800
+ # `cancel_join_thread` at creation. See later section
801
+ # [ 3b. A process won't hang when putting into a queue; ]
802
+ # for more details.
803
+ #
804
+ # Here are two example cases where library clean-up codes can run
805
+ # before `__del__` is called:
806
+ #
807
+ # 1. If we hold onto a reference to the iterator, it more often
808
+ # than not tries to do `multiprocessing` library cleaning before
809
+ # clearing the alive referenced objects (https://github.com/pytorch/pytorch/issues/48666)
810
+ # and thus prevents our cleaning-up code to run first.
811
+ #
812
+ # 2. A similar issue araises when a `DataLoader` is used in a subprocess.
813
+ # When a process ends, it shuts the all its daemonic children
814
+ # down with a SIGTERM (instead of joining them without a timeout).
815
+ # Simiarly for threads, but by a different mechanism. This fact,
816
+ # together with a few implementation details of multiprocessing, forces
817
+ # us to make workers daemonic. All of our problems arise when a
818
+ # DataLoader is used in a subprocess, and are caused by multiprocessing
819
+ # code which looks more or less like this:
820
+ #
821
+ # try:
822
+ # your_function_using_a_dataloader()
823
+ # finally:
824
+ # multiprocessing.util._exit_function()
825
+ #
826
+ # The joining/termination mentioned above happens inside
827
+ # `_exit_function()`. Now, if `your_function_using_a_dataloader()`
828
+ # throws, the stack trace stored in the exception will prevent the
829
+ # frame which uses `DataLoaderIter` to be freed. If the frame has any
830
+ # reference to the `DataLoaderIter` (e.g., in a method of the iter),
831
+ # its `__del__`, which starts the shutdown procedure, will not be
832
+ # called. That, in turn, means that workers aren't notified. Attempting
833
+ # to join in `_exit_function` will then result in a hang.
834
+ #
835
+ # For context, `_exit_function` is also registered as an `atexit` call.
836
+ # So it is unclear to me (@ssnl) why this is needed in a finally block.
837
+ # The code dates back to 2008 and there is no comment on the original
838
+ # PEP 371 or patch https://bugs.python.org/issue3050 (containing both
839
+ # the finally block and the `atexit` registration) that explains this.
840
+ #
841
+ #
842
+ # Finally, another choice is to just shutdown workers with logic in 1
843
+ # above whenever we see an error in `next`. This isn't ideal because
844
+ # a. It prevents users from using try-catch to resume data loading.
845
+ # b. It doesn't prevent hanging if users have references to the
846
+ # iterator.
847
+ #
848
+ # 3. All processes exit if any of them die unexpectedly by fatal signals.
849
+ #
850
+ # As shown above, the workers are set as daemonic children of the main
851
+ # process. However, automatic cleaning-up of such child processes only
852
+ # happens if the parent process exits gracefully (e.g., not via fatal
853
+ # signals like SIGKILL). So we must ensure that each process will exit
854
+ # even the process that should send/receive data to/from it were
855
+ # killed, i.e.,
856
+ #
857
+ # a. A process won't hang when getting from a queue.
858
+ #
859
+ # Even with carefully designed data dependencies (i.e., a `put()`
860
+ # always corresponding to a `get()`), hanging on `get()` can still
861
+ # happen when data in queue is corrupted (e.g., due to
862
+ # `cancel_join_thread` or unexpected exit).
863
+ #
864
+ # For child exit, we set a timeout whenever we try to get data
865
+ # from `data_queue`, and check the workers' status on each timeout
866
+ # and error.
867
+ # See `_DataLoaderiter._get_batch()` and
868
+ # `_DataLoaderiter._try_get_data()` for details.
869
+ #
870
+ # Additionally, for child exit on non-Windows platforms, we also
871
+ # register a SIGCHLD handler (which is supported on Windows) on
872
+ # the main process, which checks if any of the workers fail in the
873
+ # (Python) handler. This is more efficient and faster in detecting
874
+ # worker failures, compared to only using the above mechanism.
875
+ # See `DataLoader.cpp` and `_utils/signal_handling.py` for details.
876
+ #
877
+ # For `.get()` calls where the sender(s) is not the workers, we
878
+ # guard them with timeouts, and check the status of the sender
879
+ # when timeout happens:
880
+ # + in the workers, the `_utils.worker.ManagerWatchdog` class
881
+ # checks the status of the main process.
882
+ # + if `pin_memory=True`, when getting from `pin_memory_thread`,
883
+ # check `pin_memory_thread` status periodically until `.get()`
884
+ # returns or see that `pin_memory_thread` died.
885
+ #
886
+ # b. A process won't hang when putting into a queue;
887
+ #
888
+ # We use `mp.Queue` which has a separate background thread to put
889
+ # objects from an unbounded buffer array. The background thread is
890
+ # daemonic and usually automatically joined when the process
891
+ # *exits*.
892
+ #
893
+ # In case that the receiver has ended abruptly while
894
+ # reading from the pipe, the join will hang forever. The usual
895
+ # solution for this in Python is calling `q.cancel_join_thread`,
896
+ # which prevents automatically joining it when finalizing
897
+ # (exiting).
898
+ #
899
+ # Nonetheless, `cancel_join_thread` must only be called when the
900
+ # queue is **not** going to be read from or write into by another
901
+ # process, because it may hold onto a lock or leave corrupted data
902
+ # in the queue, leading other readers/writers to hang.
903
+ #
904
+ # Hence,
905
+ # + For worker processes, we only do so (for their output
906
+ # queues, i.e., `worker_result_queue`) before exiting.
907
+ # + For `pin_memory_thread`, its output queue `data_queue` is a
908
+ # `queue.Queue` that does blocking `put` if the queue is full.
909
+ # So there is no above problem, but as a result, in
910
+ # `_pin_memory_loop`, we do need to wrap the `put` in a loop
911
+ # that breaks not only upon success, but also when the main
912
+ # process stops reading, i.e., is shutting down.
913
+ # + For loader process, we `cancel_join_thread()` for all
914
+ # `_index_queues` because the whole purpose of workers and
915
+ # `pin_memory_thread` is to serve the loader process. If
916
+ # loader process is already exiting, we don't really care if
917
+ # the queues are corrupted.
918
+ #
919
+ #
920
+ # Now let's get back to 1:
921
+ # how we gracefully exit the workers when the last reference to the
922
+ # iterator is gone.
923
+ #
924
+ # To achieve this, we implement the following logic along with the design
925
+ # choices mentioned above:
926
+ #
927
+ # `workers_done_event`:
928
+ # A `multiprocessing.Event` shared among the main process and all worker
929
+ # processes. This is used to signal the workers that the iterator is
930
+ # shutting down. After it is set, they will not send processed data to
931
+ # queues anymore, and only wait for the final `None` before exiting.
932
+ # `done_event` isn't strictly needed. I.e., we can just check for `None`
933
+ # from the input queue, but it allows us to skip wasting resources
934
+ # processing data if we are already shutting down.
935
+ #
936
+ # `pin_memory_thread_done_event`:
937
+ # A `threading.Event` for a similar purpose to that of
938
+ # `workers_done_event`, but is for the `pin_memory_thread`. The reason
939
+ # that separate events are needed is that `pin_memory_thread` reads from
940
+ # the output queue of the workers. But the workers, upon seeing that
941
+ # `workers_done_event` is set, only wants to see the final `None`, and is
942
+ # not required to flush all data in the output queue (e.g., it may call
943
+ # `cancel_join_thread` on that queue if its `IterableDataset` iterator
944
+ # happens to exhaust coincidentally, which is out of the control of the
945
+ # main process). Thus, since we will exit `pin_memory_thread` before the
946
+ # workers (see below), two separete events are used.
947
+ #
948
+ # NOTE: In short, the protocol is that the main process will set these
949
+ # `done_event`s and then the corresponding processes/threads a `None`,
950
+ # and that they may exit at any time after receiving the `None`.
951
+ #
952
+ # NOTE: Using `None` as the final signal is valid, since normal data will
953
+ # always be a 2-tuple with the 1st element being the index of the data
954
+ # transferred (different from dataset index/key), and the 2nd being
955
+ # either the dataset key or the data sample (depending on which part
956
+ # of the data model the queue is at).
957
+ #
958
+ # [ worker processes ]
959
+ # While loader process is alive:
960
+ # Get from `index_queue`.
961
+ # If get anything else,
962
+ # Check `workers_done_event`.
963
+ # If set, continue to next iteration
964
+ # i.e., keep getting until see the `None`, then exit.
965
+ # Otherwise, process data:
966
+ # If is fetching from an `IterableDataset` and the iterator
967
+ # is exhausted, send an `_IterableDatasetStopIteration`
968
+ # object to signal iteration end. The main process, upon
969
+ # receiving such an object, will send `None` to this
970
+ # worker and not use the corresponding `index_queue`
971
+ # anymore.
972
+ # If timed out,
973
+ # No matter `workers_done_event` is set (still need to see `None`)
974
+ # or not, must continue to next iteration.
975
+ # (outside loop)
976
+ # If `workers_done_event` is set, (this can be False with `IterableDataset`)
977
+ # `data_queue.cancel_join_thread()`. (Everything is ending here:
978
+ # main process won't read from it;
979
+ # other workers will also call
980
+ # `cancel_join_thread`.)
981
+ #
982
+ # [ pin_memory_thread ]
983
+ # # No need to check main thread. If this thread is alive, the main loader
984
+ # # thread must be alive, because this thread is set as daemonic.
985
+ # While `pin_memory_thread_done_event` is not set:
986
+ # Get from `index_queue`.
987
+ # If timed out, continue to get in the next iteration.
988
+ # Otherwise, process data.
989
+ # While `pin_memory_thread_done_event` is not set:
990
+ # Put processed data to `data_queue` (a `queue.Queue` with blocking put)
991
+ # If timed out, continue to put in the next iteration.
992
+ # Otherwise, break, i.e., continuing to the out loop.
993
+ #
994
+ # NOTE: we don't check the status of the main thread because
995
+ # 1. if the process is killed by fatal signal, `pin_memory_thread`
996
+ # ends.
997
+ # 2. in other cases, either the cleaning-up in __del__ or the
998
+ # automatic exit of daemonic thread will take care of it.
999
+ # This won't busy-wait either because `.get(timeout)` does not
1000
+ # busy-wait.
1001
+ #
1002
+ # [ main process ]
1003
+ # In the DataLoader Iter's `__del__`
1004
+ # b. Exit `pin_memory_thread`
1005
+ # i. Set `pin_memory_thread_done_event`.
1006
+ # ii Put `None` in `worker_result_queue`.
1007
+ # iii. Join the `pin_memory_thread`.
1008
+ # iv. `worker_result_queue.cancel_join_thread()`.
1009
+ #
1010
+ # c. Exit the workers.
1011
+ # i. Set `workers_done_event`.
1012
+ # ii. Put `None` in each worker's `index_queue`.
1013
+ # iii. Join the workers.
1014
+ # iv. Call `.cancel_join_thread()` on each worker's `index_queue`.
1015
+ #
1016
+ # NOTE: (c) is better placed after (b) because it may leave corrupted
1017
+ # data in `worker_result_queue`, which `pin_memory_thread`
1018
+ # reads from, in which case the `pin_memory_thread` can only
1019
+ # happen at timeing out, which is slow. Nonetheless, same thing
1020
+ # happens if a worker is killed by signal at unfortunate times,
1021
+ # but in other cases, we are better off having a non-corrupted
1022
+ # `worker_result_queue` for `pin_memory_thread`.
1023
+ #
1024
+ # NOTE: If `pin_memory=False`, there is no `pin_memory_thread` and (b)
1025
+ # can be omitted
1026
+ #
1027
+ # NB: `done_event`s isn't strictly needed. E.g., we can just check for
1028
+ # `None` from `index_queue`, but it allows us to skip wasting resources
1029
+ # processing indices already in `index_queue` if we are already shutting
1030
+ # down.
1031
+
1032
+ def __init__(self, loader):
1033
+ super().__init__(loader)
1034
+
1035
+ self._prefetch_factor = loader.prefetch_factor
1036
+
1037
+ assert self._num_workers > 0
1038
+ assert self._prefetch_factor > 0
1039
+
1040
+ if loader.multiprocessing_context is None:
1041
+ multiprocessing_context = multiprocessing
1042
+ else:
1043
+ multiprocessing_context = loader.multiprocessing_context
1044
+
1045
+ self._worker_init_fn = loader.worker_init_fn
1046
+
1047
+ # Adds forward compatibilities so classic DataLoader can work with DataPipes:
1048
+ # Additional worker init function will take care of sharding in MP and Distributed
1049
+ if isinstance(self._dataset, (IterDataPipe, MapDataPipe)):
1050
+ self._worker_init_fn = functools.partial(
1051
+ _sharding_worker_init_fn, self._worker_init_fn, self._world_size, self._rank
1052
+ )
1053
+
1054
+ # No certainty which module multiprocessing_context is
1055
+ self._worker_result_queue = multiprocessing_context.Queue() # type: ignore[var-annotated]
1056
+ self._worker_pids_set = False
1057
+ self._shutdown = False
1058
+ self._workers_done_event = multiprocessing_context.Event()
1059
+
1060
+ self._index_queues = []
1061
+ self._workers = []
1062
+ for i in range(self._num_workers):
1063
+ # No certainty which module multiprocessing_context is
1064
+ index_queue = multiprocessing_context.Queue() # type: ignore[var-annotated]
1065
+ # Need to `cancel_join_thread` here!
1066
+ # See sections (2) and (3b) above.
1067
+ index_queue.cancel_join_thread()
1068
+ w = multiprocessing_context.Process(
1069
+ target=_worker_loop,
1070
+ args=(
1071
+ self._dataset_kind,
1072
+ self._dataset,
1073
+ index_queue,
1074
+ self._worker_result_queue,
1075
+ self._workers_done_event,
1076
+ self._auto_collation,
1077
+ self._collate_fn,
1078
+ self._drop_last,
1079
+ self._base_seed,
1080
+ self._worker_init_fn,
1081
+ i,
1082
+ self._num_workers,
1083
+ self._persistent_workers,
1084
+ self._shared_seed,
1085
+ ),
1086
+ )
1087
+ w.daemon = True
1088
+ # NB: Process.start() actually take some time as it needs to
1089
+ # start a process and pass the arguments over via a pipe.
1090
+ # Therefore, we only add a worker to self._workers list after
1091
+ # it started, so that we do not call .join() if program dies
1092
+ # before it starts, and __del__ tries to join but will get:
1093
+ # AssertionError: can only join a started process.
1094
+ w.start()
1095
+ self._index_queues.append(index_queue)
1096
+ self._workers.append(w)
1097
+
1098
+ if self._pin_memory:
1099
+ self._pin_memory_thread_done_event = threading.Event()
1100
+
1101
+ # Queue is not type-annotated
1102
+ self._data_queue = queue.Queue() # type: ignore[var-annotated]
1103
+ if self._pin_memory_device == "xpu":
1104
+ current_device = torch.xpu.current_device() # type: ignore[attr-defined]
1105
+ else:
1106
+ current_device = torch.cuda.current_device() # choose cuda for default
1107
+ pin_memory_thread = threading.Thread(
1108
+ target=_utils.pin_memory._pin_memory_loop,
1109
+ args=(
1110
+ self._worker_result_queue,
1111
+ self._data_queue,
1112
+ current_device,
1113
+ self._pin_memory_thread_done_event,
1114
+ self._pin_memory_device,
1115
+ ),
1116
+ )
1117
+ pin_memory_thread.daemon = True
1118
+ pin_memory_thread.start()
1119
+ # Similar to workers (see comment above), we only register
1120
+ # pin_memory_thread once it is started.
1121
+ self._pin_memory_thread = pin_memory_thread
1122
+ else:
1123
+ self._data_queue = self._worker_result_queue
1124
+
1125
+ # In some rare cases, persistent workers (daemonic processes)
1126
+ # would be terminated before `__del__` of iterator is invoked
1127
+ # when main process exits
1128
+ # It would cause failure when pin_memory_thread tries to read
1129
+ # corrupted data from worker_result_queue
1130
+ # atexit is used to shutdown thread and child processes in the
1131
+ # right sequence before main process exits
1132
+ if self._persistent_workers and self._pin_memory:
1133
+ import atexit
1134
+
1135
+ for w in self._workers:
1136
+ atexit.register(_MultiProcessingDataLoaderIter._clean_up_worker, w)
1137
+
1138
+ # .pid can be None only before process is spawned (not the case, so ignore)
1139
+ _utils.signal_handling._set_worker_pids(id(self), tuple(w.pid for w in self._workers)) # type: ignore[misc]
1140
+ _utils.signal_handling._set_SIGCHLD_handler()
1141
+ self._worker_pids_set = True
1142
+ self._reset(loader, first_iter=True)
1143
+
1144
+ def _reset(self, loader, first_iter=False):
1145
+ super()._reset(loader, first_iter)
1146
+ self._send_idx = 0 # idx of the next task to be sent to workers
1147
+ self._rcvd_idx = 0 # idx of the next task to be returned in __next__
1148
+ # information about data not yet yielded, i.e., tasks w/ indices in range [rcvd_idx, send_idx).
1149
+ # map: task idx => - (worker_id,) if data isn't fetched (outstanding)
1150
+ # \ (worker_id, data) if data is already fetched (out-of-order)
1151
+ self._task_info = {}
1152
+ self._tasks_outstanding = 0 # always equal to count(v for v in task_info.values() if len(v) == 1)
1153
+ # A list of booleans representing whether each worker still has work to
1154
+ # do, i.e., not having exhausted its iterable dataset object. It always
1155
+ # contains all `True`s if not using an iterable-style dataset
1156
+ # (i.e., if kind != Iterable).
1157
+ # Not that this indicates that a worker still has work to do *for this epoch*.
1158
+ # It does not mean that a worker is dead. In case of `_persistent_workers`,
1159
+ # the worker will be reset to available in the next epoch.
1160
+ self._workers_status = [True for i in range(self._num_workers)]
1161
+ # Reset the worker queue cycle so it resumes next epoch at worker 0
1162
+ self._worker_queue_idx_cycle = itertools.cycle(range(self._num_workers))
1163
+ # We resume the prefetching in case it was enabled
1164
+ if not first_iter:
1165
+ for idx in range(self._num_workers):
1166
+ self._index_queues[idx].put(_utils.worker._ResumeIteration(self._shared_seed))
1167
+ resume_iteration_cnt = self._num_workers
1168
+ while resume_iteration_cnt > 0:
1169
+ return_idx, return_data = self._get_data()
1170
+ if isinstance(return_idx, _utils.worker._ResumeIteration):
1171
+ assert return_data is None
1172
+ resume_iteration_cnt -= 1
1173
+ # prime the prefetch loop
1174
+ for _ in range(self._prefetch_factor * self._num_workers):
1175
+ self._try_put_index()
1176
+
1177
+ def _try_get_data(self, timeout=_utils.MP_STATUS_CHECK_INTERVAL):
1178
+ # Tries to fetch data from `self._data_queue` once for a given timeout.
1179
+ # This can also be used as inner loop of fetching without timeout, with
1180
+ # the sender status as the loop condition.
1181
+ #
1182
+ # This raises a `RuntimeError` if any worker died expectedly. This error
1183
+ # can come from either the SIGCHLD handler in `_utils/signal_handling.py`
1184
+ # (only for non-Windows platforms), or the manual check below on errors
1185
+ # and timeouts.
1186
+ #
1187
+ # Returns a 2-tuple:
1188
+ # (bool: whether successfully get data, any: data if successful else None)
1189
+ try:
1190
+ data = self._data_queue.get(timeout=timeout)
1191
+ return (True, data)
1192
+ except Exception as e:
1193
+ # At timeout and error, we manually check whether any worker has
1194
+ # failed. Note that this is the only mechanism for Windows to detect
1195
+ # worker failures.
1196
+ failed_workers = []
1197
+ for worker_id, w in enumerate(self._workers):
1198
+ if self._workers_status[worker_id] and not w.is_alive():
1199
+ failed_workers.append(w)
1200
+ self._mark_worker_as_unavailable(worker_id)
1201
+ if len(failed_workers) > 0:
1202
+ pids_str = ", ".join(str(w.pid) for w in failed_workers)
1203
+ raise RuntimeError("DataLoader worker (pid(s) {}) exited unexpectedly".format(pids_str)) from e
1204
+ if isinstance(e, queue.Empty):
1205
+ return (False, None)
1206
+ import errno
1207
+ import tempfile
1208
+
1209
+ try:
1210
+ # Raise an exception if we are this close to the FDs limit.
1211
+ # Apparently, trying to open only one file is not a sufficient
1212
+ # test.
1213
+ # See NOTE [ DataLoader on Linux and open files limit ]
1214
+ fds_limit_margin = 10
1215
+ fs = [tempfile.NamedTemporaryFile() for i in range(fds_limit_margin)]
1216
+ except OSError as e:
1217
+ if e.errno == errno.EMFILE:
1218
+ raise RuntimeError(
1219
+ "Too many open files. Communication with the"
1220
+ " workers is no longer possible. Please increase the"
1221
+ " limit using `ulimit -n` in the shell or change the"
1222
+ " sharing strategy by calling"
1223
+ " `torch.multiprocessing.set_sharing_strategy('file_system')`"
1224
+ " at the beginning of your code"
1225
+ ) from None
1226
+ raise
1227
+
1228
+ # NOTE [ DataLoader on Linux and open files limit ]
1229
+ #
1230
+ # On Linux when DataLoader is used with multiprocessing we pass the data between
1231
+ # the root process and the workers through SHM files. We remove those files from
1232
+ # the filesystem as soon as they are created and keep them alive by
1233
+ # passing around their file descriptors through AF_UNIX sockets. (See
1234
+ # docs/source/multiprocessing.rst and 'Multiprocessing Technical Notes` in
1235
+ # the wiki (https://github.com/pytorch/pytorch/wiki).)
1236
+ #
1237
+ # This sometimes leads us to exceeding the open files limit. When that happens,
1238
+ # and the offending file descriptor is coming over a socket, the `socket` Python
1239
+ # package silently strips the file descriptor from the message, setting only the
1240
+ # `MSG_CTRUNC` flag (which might be a bit misleading since the manpage says that
1241
+ # it _indicates that some control data were discarded due to lack of space in
1242
+ # the buffer for ancillary data_). This might reflect the C implementation of
1243
+ # AF_UNIX sockets.
1244
+ #
1245
+ # This behaviour can be reproduced with the script and instructions at the
1246
+ # bottom of this note.
1247
+ #
1248
+ # When that happens, the standard Python `multiprocessing` (and not
1249
+ # `torch.multiprocessing`) raises a `RuntimeError: received 0 items of ancdata`
1250
+ #
1251
+ # Sometimes, instead of the FD being stripped, you may get an `OSError:
1252
+ # Too many open files`, both in the script below and in DataLoader. However,
1253
+ # this is rare and seems to be nondeterministic.
1254
+ #
1255
+ #
1256
+ # #!/usr/bin/env python3
1257
+ # import sys
1258
+ # import socket
1259
+ # import os
1260
+ # import array
1261
+ # import shutil
1262
+ # import socket
1263
+ #
1264
+ #
1265
+ # if len(sys.argv) != 4:
1266
+ # print("Usage: ", sys.argv[0], " tmp_dirname iteration (send|recv)")
1267
+ # sys.exit(1)
1268
+ #
1269
+ # if __name__ == '__main__':
1270
+ # dirname = sys.argv[1]
1271
+ # sock_path = dirname + "/sock"
1272
+ # iterations = int(sys.argv[2])
1273
+ # def dummy_path(i):
1274
+ # return dirname + "/" + str(i) + ".dummy"
1275
+ #
1276
+ #
1277
+ # if sys.argv[3] == 'send':
1278
+ # while not os.path.exists(sock_path):
1279
+ # pass
1280
+ # client = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
1281
+ # client.connect(sock_path)
1282
+ # for i in range(iterations):
1283
+ # fd = os.open(dummy_path(i), os.O_WRONLY | os.O_CREAT)
1284
+ # ancdata = array.array('i', [fd])
1285
+ # msg = bytes([i % 256])
1286
+ # print("Sending fd ", fd, " (iteration #", i, ")")
1287
+ # client.sendmsg([msg], [(socket.SOL_SOCKET, socket.SCM_RIGHTS, ancdata)])
1288
+ #
1289
+ #
1290
+ # else:
1291
+ # assert sys.argv[3] == 'recv'
1292
+ #
1293
+ # if os.path.exists(dirname):
1294
+ # raise Exception("Directory exists")
1295
+ #
1296
+ # os.mkdir(dirname)
1297
+ #
1298
+ # print("Opening socket...")
1299
+ # server = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
1300
+ # server.bind(sock_path)
1301
+ #
1302
+ # print("Listening...")
1303
+ # for i in range(iterations):
1304
+ # a = array.array('i')
1305
+ # msg, ancdata, flags, addr = server.recvmsg(1, socket.CMSG_SPACE(a.itemsize))
1306
+ # assert(len(ancdata) == 1)
1307
+ # cmsg_level, cmsg_type, cmsg_data = ancdata[0]
1308
+ # a.frombytes(cmsg_data)
1309
+ # print("Received fd ", a[0], " (iteration #", i, ")")
1310
+ #
1311
+ # shutil.rmtree(dirname)
1312
+ #
1313
+ # Steps to reproduce:
1314
+ #
1315
+ # 1. Run two shells and set lower file descriptor limit in the receiving one:
1316
+ # (shell1) ulimit -n 1020
1317
+ # (shell2) ulimit -n 1022
1318
+ #
1319
+ # 2. Run the script above with the `recv` option in the first shell
1320
+ # (shell1) ./test_socket.py sock_tmp 1017 recv
1321
+ #
1322
+ # 3. Run the script with the `send` option in the second shell:
1323
+ # (shell2) ./test_socket.py sock_tmp 1017 send
1324
+
1325
+ def _get_data(self):
1326
+ # Fetches data from `self._data_queue`.
1327
+ #
1328
+ # We check workers' status every `MP_STATUS_CHECK_INTERVAL` seconds,
1329
+ # which we achieve by running `self._try_get_data(timeout=MP_STATUS_CHECK_INTERVAL)`
1330
+ # in a loop. This is the only mechanism to detect worker failures for
1331
+ # Windows. For other platforms, a SIGCHLD handler is also used for
1332
+ # worker failure detection.
1333
+ #
1334
+ # If `pin_memory=True`, we also need check if `pin_memory_thread` had
1335
+ # died at timeouts.
1336
+ if self._timeout > 0:
1337
+ success, data = self._try_get_data(self._timeout)
1338
+ if success:
1339
+ return data
1340
+ else:
1341
+ raise RuntimeError("DataLoader timed out after {} seconds".format(self._timeout))
1342
+ elif self._pin_memory:
1343
+ while self._pin_memory_thread.is_alive():
1344
+ success, data = self._try_get_data()
1345
+ if success:
1346
+ return data
1347
+ else:
1348
+ # while condition is false, i.e., pin_memory_thread died.
1349
+ raise RuntimeError("Pin memory thread exited unexpectedly")
1350
+ # In this case, `self._data_queue` is a `queue.Queue`,. But we don't
1351
+ # need to call `.task_done()` because we don't use `.join()`.
1352
+ else:
1353
+ while True:
1354
+ success, data = self._try_get_data()
1355
+ if success:
1356
+ return data
1357
+
1358
+ def _next_data(self):
1359
+ while True:
1360
+ # If the worker responsible for `self._rcvd_idx` has already ended
1361
+ # and was unable to fulfill this task (due to exhausting an `IterableDataset`),
1362
+ # we try to advance `self._rcvd_idx` to find the next valid index.
1363
+ #
1364
+ # This part needs to run in the loop because both the `self._get_data()`
1365
+ # call and `_IterableDatasetStopIteration` check below can mark
1366
+ # extra worker(s) as dead.
1367
+ while self._rcvd_idx < self._send_idx:
1368
+ info = self._task_info[self._rcvd_idx]
1369
+ worker_id = info[0]
1370
+ if len(info) == 2 or self._workers_status[worker_id]: # has data or is still active
1371
+ break
1372
+ del self._task_info[self._rcvd_idx]
1373
+ self._rcvd_idx += 1
1374
+ else:
1375
+ # no valid `self._rcvd_idx` is found (i.e., didn't break)
1376
+ if not self._persistent_workers:
1377
+ self._shutdown_workers()
1378
+ raise StopIteration
1379
+
1380
+ # Now `self._rcvd_idx` is the batch index we want to fetch
1381
+
1382
+ # Check if the next sample has already been generated
1383
+ if len(self._task_info[self._rcvd_idx]) == 2:
1384
+ data = self._task_info.pop(self._rcvd_idx)[1]
1385
+ return self._process_data(data)
1386
+
1387
+ assert not self._shutdown and self._tasks_outstanding > 0
1388
+ idx, data = self._get_data()
1389
+ self._tasks_outstanding -= 1
1390
+ if self._dataset_kind == _DatasetKind.Iterable:
1391
+ # Check for _IterableDatasetStopIteration
1392
+ if isinstance(data, _utils.worker._IterableDatasetStopIteration):
1393
+ if self._persistent_workers:
1394
+ self._workers_status[data.worker_id] = False
1395
+ else:
1396
+ self._mark_worker_as_unavailable(data.worker_id)
1397
+ self._try_put_index()
1398
+ continue
1399
+
1400
+ if idx != self._rcvd_idx:
1401
+ # store out-of-order samples
1402
+ self._task_info[idx] += (data,)
1403
+ else:
1404
+ del self._task_info[idx]
1405
+ return self._process_data(data)
1406
+
1407
+ def _try_put_index(self):
1408
+ assert self._tasks_outstanding < self._prefetch_factor * self._num_workers
1409
+
1410
+ try:
1411
+ index = self._next_index()
1412
+ except StopIteration:
1413
+ return
1414
+ for _ in range(self._num_workers): # find the next active worker, if any
1415
+ worker_queue_idx = next(self._worker_queue_idx_cycle)
1416
+ if self._workers_status[worker_queue_idx]:
1417
+ break
1418
+ else:
1419
+ # not found (i.e., didn't break)
1420
+ return
1421
+
1422
+ self._index_queues[worker_queue_idx].put((self._send_idx, index))
1423
+ self._task_info[self._send_idx] = (worker_queue_idx,)
1424
+ self._tasks_outstanding += 1
1425
+ self._send_idx += 1
1426
+
1427
+ def _process_data(self, data):
1428
+ self._rcvd_idx += 1
1429
+ self._try_put_index()
1430
+ if isinstance(data, ExceptionWrapper):
1431
+ data.reraise()
1432
+ return data
1433
+
1434
+ def _mark_worker_as_unavailable(self, worker_id, shutdown=False):
1435
+ # Mark a worker as having finished its work e.g., due to
1436
+ # exhausting an `IterableDataset`. This should be used only when this
1437
+ # `_MultiProcessingDataLoaderIter` is going to continue running.
1438
+
1439
+ assert self._workers_status[worker_id] or (self._persistent_workers and shutdown)
1440
+
1441
+ # Signal termination to that specific worker.
1442
+ q = self._index_queues[worker_id]
1443
+ # Indicate that no more data will be put on this queue by the current
1444
+ # process.
1445
+ q.put(None)
1446
+
1447
+ # Note that we don't actually join the worker here, nor do we remove the
1448
+ # worker's pid from C side struct because (1) joining may be slow, and
1449
+ # (2) since we don't join, the worker may still raise error, and we
1450
+ # prefer capturing those, rather than ignoring them, even though they
1451
+ # are raised after the worker has finished its job.
1452
+ # Joinning is deferred to `_shutdown_workers`, which it is called when
1453
+ # all workers finish their jobs (e.g., `IterableDataset` replicas) or
1454
+ # when this iterator is garbage collected.
1455
+
1456
+ self._workers_status[worker_id] = False
1457
+
1458
+ assert self._workers_done_event.is_set() == shutdown
1459
+
1460
+ def _shutdown_workers(self):
1461
+ # Called when shutting down this `_MultiProcessingDataLoaderIter`.
1462
+ # See NOTE [ Data Loader Multiprocessing Shutdown Logic ] for details on
1463
+ # the logic of this function.
1464
+ if _utils is None or _utils.python_exit_status is True or _utils.python_exit_status is None:
1465
+ # See (2) of the note. If Python is shutting down, do no-op.
1466
+ return
1467
+ # Normal exit when last reference is gone / iterator is depleted.
1468
+ # See (1) and the second half of the note.
1469
+ if not self._shutdown:
1470
+ self._shutdown = True
1471
+ try:
1472
+ # Normal exit when last reference is gone / iterator is depleted.
1473
+ # See (1) and the second half of the note.
1474
+
1475
+ # Exit `pin_memory_thread` first because exiting workers may leave
1476
+ # corrupted data in `worker_result_queue` which `pin_memory_thread`
1477
+ # reads from.
1478
+ if hasattr(self, "_pin_memory_thread"):
1479
+ # Use hasattr in case error happens before we set the attribute.
1480
+ self._pin_memory_thread_done_event.set()
1481
+ # Send something to pin_memory_thread in case it is waiting
1482
+ # so that it can wake up and check `pin_memory_thread_done_event`
1483
+ self._worker_result_queue.put((None, None))
1484
+ self._pin_memory_thread.join()
1485
+ self._worker_result_queue.cancel_join_thread()
1486
+ self._worker_result_queue.close()
1487
+
1488
+ # Exit workers now.
1489
+ self._workers_done_event.set()
1490
+ for worker_id in range(len(self._workers)):
1491
+ # Get number of workers from `len(self._workers)` instead of
1492
+ # `self._num_workers` in case we error before starting all
1493
+ # workers.
1494
+ # If we are using workers_status with persistent_workers
1495
+ # we have to shut it down because the worker is paused
1496
+ if self._persistent_workers or self._workers_status[worker_id]:
1497
+ self._mark_worker_as_unavailable(worker_id, shutdown=True)
1498
+ for w in self._workers:
1499
+ # We should be able to join here, but in case anything went
1500
+ # wrong, we set a timeout and if the workers fail to join,
1501
+ # they are killed in the `finally` block.
1502
+ w.join(timeout=_utils.MP_STATUS_CHECK_INTERVAL)
1503
+ for q in self._index_queues:
1504
+ q.cancel_join_thread()
1505
+ q.close()
1506
+ finally:
1507
+ # Even though all this function does is putting into queues that
1508
+ # we have called `cancel_join_thread` on, weird things can
1509
+ # happen when a worker is killed by a signal, e.g., hanging in
1510
+ # `Event.set()`. So we need to guard this with SIGCHLD handler,
1511
+ # and remove pids from the C side data structure only at the
1512
+ # end.
1513
+ #
1514
+ # FIXME: Unfortunately, for Windows, we are missing a worker
1515
+ # error detection mechanism here in this function, as it
1516
+ # doesn't provide a SIGCHLD handler.
1517
+ if self._worker_pids_set:
1518
+ _utils.signal_handling._remove_worker_pids(id(self))
1519
+ self._worker_pids_set = False
1520
+ for w in self._workers:
1521
+ if w.is_alive():
1522
+ # Existing mechanisms try to make the workers exit
1523
+ # peacefully, but in case that we unfortunately reach
1524
+ # here, which we shouldn't, (e.g., pytorch/pytorch#39570),
1525
+ # we kill the worker.
1526
+ w.terminate()
1527
+
1528
+ # staticmethod is used to remove reference to `_MultiProcessingDataLoaderIter`
1529
+ @staticmethod
1530
+ def _clean_up_worker(w):
1531
+ try:
1532
+ w.join(timeout=_utils.MP_STATUS_CHECK_INTERVAL)
1533
+ finally:
1534
+ if w.is_alive():
1535
+ w.terminate()
1536
+
1537
+ def __del__(self):
1538
+ self._shutdown_workers()