Benchmark numpy¶
This example benchmarks the speed of loading data in different formats.
See Case Studies / Data Format for the detail of how data format and the loading function affects the performance of the training pipeline.
Example
$ numactl --membind 0 --cpubind 0 python benchmark_numpy.py --output results.csv
# Plot results
$ python benchmark_numpy_plot.py --input results.csv --output plot.png
Source¶
Source
Click here to see the source.
1#!/usr/bin/env python3
2# Copyright (c) Meta Platforms, Inc. and affiliates.
3# All rights reserved.
4#
5# This source code is licensed under the BSD-style license found in the
6# LICENSE file in the root directory of this source tree.
7
8"""This example benchmarks the speed of loading data in different formats.
9
10See `Case Studies / Data Format <../case_studies/data_format.html>`_ for
11the detail of how data format and the loading function affects
12the performance of the training pipeline.
13
14**Example**
15
16.. code-block:: shell
17
18 $ numactl --membind 0 --cpubind 0 python benchmark_numpy.py --output results.csv
19 # Plot results
20 $ python benchmark_numpy_plot.py --input results.csv --output plot.png
21
22"""
23
24__all__ = [
25 "main",
26 "get_mock_data",
27 "load_npy",
28 "load_npy_spdl",
29 "load_npz",
30 "load_npz_spdl",
31 "load_torch",
32 "BenchmarkConfig",
33]
34
35
36import argparse
37import os
38from collections.abc import Callable
39from dataclasses import dataclass
40from functools import partial
41from io import BytesIO
42
43import numpy as np
44import spdl.io
45import torch
46from numpy.typing import NDArray
47
48try:
49 from examples.benchmark_utils import ( # pyre-ignore[21]
50 BenchmarkResult,
51 BenchmarkRunner,
52 ExecutorType,
53 get_default_result_path,
54 save_results_to_csv,
55 )
56except ImportError:
57 from spdl.examples.benchmark_utils import (
58 BenchmarkResult,
59 BenchmarkRunner,
60 ExecutorType,
61 get_default_result_path,
62 save_results_to_csv,
63 )
64
65
66DEFAULT_RESULT_PATH: str = get_default_result_path(__file__)
67
68
69def load_npy(items: list[bytes]) -> list[NDArray]:
70 """Load arrays from serialized NPY binary strings using :py:func:`numpy.load`."""
71 return [np.load(BytesIO(item), allow_pickle=False) for item in items]
72
73
74def load_npy_spdl(items: list[bytes]) -> list[NDArray]:
75 """Load arrays from serialized NPY binary strings using :py:func:`spdl.io.load_npy`."""
76 return [spdl.io.load_npy(item) for item in items]
77
78
79def load_npz(item: bytes) -> list[NDArray]:
80 """Load arrays from a serialized NPZ binary string using :py:func:`numpy.load`."""
81 data = np.load(BytesIO(item))
82 return list(data.values())
83
84
85def load_npz_spdl(item: bytes) -> list[NDArray]:
86 """Load arrays from serialized NPZ binary strings using :py:func:`spdl.io.load_npz`."""
87 data = spdl.io.load_npz(item)
88 return list(data.values())
89
90
91def load_torch(item: bytes) -> list[NDArray]:
92 """Load arrays from a serialized PyTorch state dict."""
93 return list(torch.load(BytesIO(item)).values())
94
95
96def _get_load_fn(
97 data_format: str, impl: str
98) -> Callable[[list[bytes]], list[NDArray]] | Callable[[bytes], list[NDArray]]:
99 match data_format:
100 case "torch":
101 return load_torch
102 case "npy":
103 if impl == "spdl":
104 return load_npy_spdl
105 return load_npy
106 case "npz":
107 if impl == "spdl":
108 return load_npz_spdl
109 return load_npz
110 case _:
111 raise ValueError(f"Unexpected data format: {data_format}")
112
113
114def _dump_np(arr: NDArray | dict[str, NDArray], compressed: bool = False) -> bytes:
115 with BytesIO() as buf:
116 if isinstance(arr, dict):
117 if compressed:
118 np.savez_compressed(buf, allow_pickle=False, **arr)
119 else:
120 np.savez(buf, allow_pickle=False, **arr)
121 else:
122 np.save(buf, arr, allow_pickle=False)
123 buf.seek(0)
124 return buf.read()
125
126
127def _dump_torch(arr: dict[str, NDArray]) -> bytes:
128 with BytesIO() as buf:
129 torch.save({k: torch.from_numpy(v) for k, v in arr.items()}, buf)
130 buf.seek(0)
131 return buf.read()
132
133
134def get_mock_data(format: str, compressed: bool = False) -> tuple[bytes, bytes] | bytes:
135 """Generate a single sample in the given format.
136
137 The mock data resemboles an RGB image and its segmentation labels.
138
139 Args:
140 format: One of ``"npz"``, ``"npy"`` or ``"torch"``.
141 compressed: If ``True``, NPZ file is compressed.
142 (i.e. :py:func:`numpy.savez_compressed` is used.)
143
144 Returns:
145 Serialized mock arrays. If ``"npy"`` then arrays are serialized
146 separately. Otherwise arrays are bundled together.
147 """
148 # pyrefly: ignore [no-matching-overload]
149 img = np.random.randint(256, size=(3, 640, 480), dtype=np.uint8)
150 # pyrefly: ignore [no-matching-overload]
151 lbl = np.random.randint(256, size=(640, 480), dtype=np.uint8)
152
153 match format:
154 case "npz":
155 return _dump_np({"img": img, "lbl": lbl}, compressed=compressed)
156 case "npy":
157 return _dump_np(img), _dump_np(lbl)
158 case "torch":
159 return _dump_torch({"img": img, "lbl": lbl})
160 case _:
161 raise ValueError(f"Unexpected `format`: {format}")
162
163
164@dataclass
165class BenchmarkConfig:
166 """BenchmarkConfig()
167
168 Configuration for a single benchmark run."""
169
170 data_format: str
171 """Data format (``"npy"``, ``"npz"``, or ``"torch"``)"""
172
173 compressed: bool
174 """Whether NPZ file is compressed"""
175
176 impl: str
177 """Implementation (``"np"``, ``"spdl"``, or ``"torch"``)"""
178
179 num_workers: int
180 """Number of concurrent workers"""
181
182
183def _parse_args() -> argparse.Namespace:
184 """Parse command line arguments.
185
186 Returns:
187 Parsed arguments.
188 """
189 parser = argparse.ArgumentParser(
190 description="Benchmark data format loading performance"
191 )
192 parser.add_argument(
193 "--output",
194 type=lambda p: os.path.realpath(p),
195 default=DEFAULT_RESULT_PATH,
196 help="Output path for the results",
197 )
198 return parser.parse_args()
199
200
201def main() -> None:
202 """The entrypoint from CLI."""
203 args = _parse_args()
204
205 # Define explicit configuration lists
206 worker_counts = [32, 16, 8, 4, 2, 1]
207 executor_types = [ExecutorType.PROCESS, ExecutorType.THREAD]
208
209 # Define benchmark configurations
210 # (data_format, compressed, impl)
211 data_configs = [
212 ("torch", False, "torch"),
213 ("npy", False, "np"),
214 ("npy", False, "spdl"),
215 ("npz", False, "np"),
216 ("npz", True, "np"),
217 ("npz", False, "spdl"),
218 ("npz", True, "spdl"),
219 ]
220
221 results: list[BenchmarkResult[BenchmarkConfig]] = []
222 iterations = 1000
223 num_runs = 5
224
225 for num_workers in worker_counts:
226 for executor_type in executor_types:
227 with BenchmarkRunner(
228 executor_type=executor_type,
229 num_workers=num_workers,
230 warmup_iterations=30 * num_workers,
231 ) as runner:
232 for data_format, compressed, impl in data_configs:
233 data = get_mock_data(data_format, compressed)
234
235 load_fn = _get_load_fn(data_format, impl)
236
237 result, _ = runner.run(
238 BenchmarkConfig(
239 data_format=data_format,
240 compressed=compressed,
241 impl=impl,
242 num_workers=num_workers,
243 ),
244 partial(load_fn, data),
245 iterations,
246 num_runs=num_runs,
247 )
248
249 results.append(result)
250 print(
251 f"{data_format},{compressed},{impl},{executor_type.value},{num_workers},{result.qps:.1f}"
252 )
253
254 save_results_to_csv(results, args.output)
255 plot_output = args.output.replace(".csv", ".png")
256 print(
257 f"\nBenchmark complete. To generate plots, run:\n"
258 f"python benchmark_numpy_plot.py --input {args.output} --output {plot_output}"
259 )
260
261
262if __name__ == "__main__":
263 main()
API Reference¶
Functions
- get_mock_data(format: str, compressed: bool = False) tuple[bytes, bytes] | bytes[source]¶
Generate a single sample in the given format.
The mock data resemboles an RGB image and its segmentation labels.
- Parameters:
format – One of
"npz","npy"or"torch".compressed – If
True, NPZ file is compressed. (i.e.numpy.savez_compressed()is used.)
- Returns:
Serialized mock arrays. If
"npy"then arrays are serialized separately. Otherwise arrays are bundled together.
- load_npy(items: list[bytes]) list[NDArray][source]¶
Load arrays from serialized NPY binary strings using
numpy.load().
- load_npy_spdl(items: list[bytes]) list[NDArray][source]¶
Load arrays from serialized NPY binary strings using
spdl.io.load_npy().
- load_npz(item: bytes) list[NDArray][source]¶
Load arrays from a serialized NPZ binary string using
numpy.load().
- load_npz_spdl(item: bytes) list[NDArray][source]¶
Load arrays from serialized NPZ binary strings using
spdl.io.load_npz().
Classes