pmpp 0.1.3__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.
- pmpp/FFT_distributed.py +851 -0
- pmpp/__init__.py +9 -0
- pmpp/boltzmann.py +484 -0
- pmpp/camels_io.py +370 -0
- pmpp/configuration.py +569 -0
- pmpp/corrections/__init__.py +90 -0
- pmpp/corrections/combined.py +41 -0
- pmpp/corrections/common.py +184 -0
- pmpp/corrections/core.py +397 -0
- pmpp/corrections/mesh_cnn.py +333 -0
- pmpp/corrections/pgd.py +255 -0
- pmpp/corrections/radial.py +245 -0
- pmpp/corrections/softening.py +81 -0
- pmpp/corrections/window.py +285 -0
- pmpp/cosmo.py +514 -0
- pmpp/enmesh.py +169 -0
- pmpp/fft.py +155 -0
- pmpp/gather.py +614 -0
- pmpp/gravity.py +747 -0
- pmpp/growth.py +111 -0
- pmpp/halo_moving.py +3623 -0
- pmpp/lpt.py +299 -0
- pmpp/mesh_halo.py +198 -0
- pmpp/modes.py +458 -0
- pmpp/multigpu_configuration.py +263 -0
- pmpp/nbody.py +642 -0
- pmpp/nbody_observers.py +145 -0
- pmpp/ode_util.py +500 -0
- pmpp/particles.py +2092 -0
- pmpp/plotting_utils.py +195 -0
- pmpp/potential_correction.py +86 -0
- pmpp/power_spectrum.py +560 -0
- pmpp/scatter.py +421 -0
- pmpp/steps.py +1060 -0
- pmpp/utils.py +490 -0
- pmpp-0.1.3.dist-info/METADATA +242 -0
- pmpp-0.1.3.dist-info/RECORD +40 -0
- pmpp-0.1.3.dist-info/WHEEL +4 -0
- pmpp-0.1.3.dist-info/licenses/LICENSE +29 -0
- pmpp-0.1.3.dist-info/licenses/THIRD_PARTY_NOTICES.md +35 -0
pmpp/FFT_distributed.py
ADDED
|
@@ -0,0 +1,851 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Author: Nicolas Payot
|
|
3
|
+
Date: 06/18/2024
|
|
4
|
+
|
|
5
|
+
This Python script handles large scale Fourier Transforms utilizing multiple GPUs for computation. In particular, it
|
|
6
|
+
provides the functionality to distribute an ndarray across multiple GPUs for efficient computation of the Fourier
|
|
7
|
+
Transform.
|
|
8
|
+
|
|
9
|
+
The primary functions are split_array_for_gpus, distribute_array_on_gpus, create_sharded_fft, and create_ffts which
|
|
10
|
+
facilitate array division, allocation of array segments to GPUs, creating sharded versions of an FFT function, and
|
|
11
|
+
creating the main FFT functions for complex and real input that work in a distributed fashion across GPUs. Also, there
|
|
12
|
+
is a test function to compare the output and performance of multi-GPU FFT against a reference FFT function.
|
|
13
|
+
|
|
14
|
+
These functions are highly beneficial when dealing with extremely large arrays where parallelization of computation can
|
|
15
|
+
lead to significant performance improvements. The code leverages both numpy and jax libraries for computation and
|
|
16
|
+
parallelization.
|
|
17
|
+
|
|
18
|
+
Note: The script expects a computing environment with multiple GPUs available.
|
|
19
|
+
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import numpy as np
|
|
23
|
+
from typing import Callable, Tuple
|
|
24
|
+
from functools import partial
|
|
25
|
+
|
|
26
|
+
import jax
|
|
27
|
+
from jax import lax, Array
|
|
28
|
+
from jax import custom_vjp
|
|
29
|
+
from jax.experimental import mesh_utils
|
|
30
|
+
from jax.experimental.custom_partitioning import custom_partitioning
|
|
31
|
+
from jax.sharding import Mesh, PartitionSpec as P, NamedSharding
|
|
32
|
+
import jax.tree_util as tree
|
|
33
|
+
import jax.numpy as jnp
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def split_array_for_gpus(array: np.ndarray, num_gpus: int, axis: int = 1) -> Array:
|
|
37
|
+
"""Split an array into equal chunks for host-side GPU distribution.
|
|
38
|
+
|
|
39
|
+
Parameters
|
|
40
|
+
----------
|
|
41
|
+
array : numpy.ndarray
|
|
42
|
+
Input host array.
|
|
43
|
+
num_gpus : int
|
|
44
|
+
Number of device chunks to create.
|
|
45
|
+
axis : int, optional
|
|
46
|
+
Axis to split.
|
|
47
|
+
|
|
48
|
+
Returns
|
|
49
|
+
-------
|
|
50
|
+
numpy.ndarray
|
|
51
|
+
Array of host chunks, one per target GPU.
|
|
52
|
+
"""
|
|
53
|
+
# Split the array equally along the second dimension for each GPU
|
|
54
|
+
return jnp.array(jnp.array_split(array, num_gpus, axis=axis))
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def distribute_array_on_gpus_old(array: np.ndarray, compute_mesh: Mesh, partition: P,
|
|
58
|
+
axis_name: str = "gpus") -> jnp.ndarray:
|
|
59
|
+
"""
|
|
60
|
+
Distributes the given array across multiple GPUs for computation. The distribution follows a partition configuration
|
|
61
|
+
and an axis along which the array should be split for distribution.
|
|
62
|
+
ea
|
|
63
|
+
Args:
|
|
64
|
+
array (np.ndarray): The input array to be distributed.
|
|
65
|
+
compute_mesh (Mesh): The compute mesh that defines the layout of GPUs.
|
|
66
|
+
partition (P): The partition configuration for distributing the array.
|
|
67
|
+
axis_name (str): The axis along which the array needs to be split for distribution. Defaults to "gpus".
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
jnp.ndarray: A Jax array distributed across multiple GPUs.
|
|
71
|
+
|
|
72
|
+
Note:
|
|
73
|
+
The function 'split_array_for_gpus' is used to split the array equally for each GPU before distributing. Ensure that
|
|
74
|
+
the number of GPUs evenly divides the dimension corresponding to `axis_name` of `array` which is governed by the 'partition' configuration.
|
|
75
|
+
"""
|
|
76
|
+
# Get the number of GPUs
|
|
77
|
+
num_gpus = len(compute_mesh.devices)
|
|
78
|
+
# Split the array equally for each GPU
|
|
79
|
+
array_parts = split_array_for_gpus(array, num_gpus, partition.index(axis_name))
|
|
80
|
+
with compute_mesh:
|
|
81
|
+
# Distribute the array parts across different GPUs
|
|
82
|
+
array_parts_device = [jax.device_put(part, device) for part, device in zip(array_parts, compute_mesh.devices)]
|
|
83
|
+
array_distributed = jax.make_array_from_single_device_arrays(array.shape,
|
|
84
|
+
NamedSharding(compute_mesh, partition),
|
|
85
|
+
array_parts_device)
|
|
86
|
+
return array_distributed
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def distribute_array_on_gpus(array: np.ndarray, compute_mesh: Mesh, partition: P) -> jnp.ndarray:
|
|
90
|
+
"""Place a host array onto a compute mesh with explicit sharding.
|
|
91
|
+
|
|
92
|
+
Parameters
|
|
93
|
+
----------
|
|
94
|
+
array : numpy.ndarray
|
|
95
|
+
Input host array already shaped consistently with ``partition``.
|
|
96
|
+
compute_mesh : Mesh
|
|
97
|
+
Device mesh defining the target sharding.
|
|
98
|
+
partition : PartitionSpec
|
|
99
|
+
Partition specification for the output array.
|
|
100
|
+
|
|
101
|
+
Returns
|
|
102
|
+
-------
|
|
103
|
+
jax.Array
|
|
104
|
+
Sharded array placed on ``compute_mesh``.
|
|
105
|
+
"""
|
|
106
|
+
sharding = NamedSharding(compute_mesh, partition)
|
|
107
|
+
array_parts_device = [jax.device_put(array[i], device=d) for d, i in
|
|
108
|
+
sharding.addressable_devices_indices_map(array.shape).items()]
|
|
109
|
+
array_distributed = jax.make_array_from_single_device_arrays(array.shape, sharding, array_parts_device)
|
|
110
|
+
return array_distributed
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def create_sharded_fft(basic_fft: Callable, partition_spec: P, global_mesh: Mesh = None):
|
|
114
|
+
"""Wrap a local FFT kernel in JAX custom partitioning metadata.
|
|
115
|
+
|
|
116
|
+
Parameters
|
|
117
|
+
----------
|
|
118
|
+
basic_fft : callable
|
|
119
|
+
Local FFT kernel operating on one logical shard layout.
|
|
120
|
+
partition_spec : PartitionSpec
|
|
121
|
+
Target sharding of the FFT input and output.
|
|
122
|
+
global_mesh : Mesh, optional
|
|
123
|
+
Fallback mesh used when the input sharding object does not carry one
|
|
124
|
+
directly.
|
|
125
|
+
|
|
126
|
+
Returns
|
|
127
|
+
-------
|
|
128
|
+
callable
|
|
129
|
+
FFT wrapper carrying custom partitioning rules.
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
@custom_partitioning
|
|
133
|
+
def sharded_fft(x):
|
|
134
|
+
"""Applies the FFT function to the input array with sharding applied.
|
|
135
|
+
|
|
136
|
+
Parameters
|
|
137
|
+
----------
|
|
138
|
+
x
|
|
139
|
+
Input array transformed by this FFT wrapper."""
|
|
140
|
+
return basic_fft(x)
|
|
141
|
+
|
|
142
|
+
def supported_sharding(sharding, shape):
|
|
143
|
+
"""Returns a NamedSharding based on the input sharding and the partition specification.
|
|
144
|
+
|
|
145
|
+
Parameters
|
|
146
|
+
----------
|
|
147
|
+
shape
|
|
148
|
+
Array shape used when deriving output sharding."""
|
|
149
|
+
if isinstance(sharding, NamedSharding):
|
|
150
|
+
mesh = sharding.mesh
|
|
151
|
+
else:
|
|
152
|
+
mesh = global_mesh if global_mesh else sharding.mesh
|
|
153
|
+
return NamedSharding(mesh, partition_spec)
|
|
154
|
+
|
|
155
|
+
def partition(mesh, arg_shapes, result_shape):
|
|
156
|
+
"""Defines how to partition the FFT computation across the devices.
|
|
157
|
+
|
|
158
|
+
Parameters
|
|
159
|
+
----------
|
|
160
|
+
mesh
|
|
161
|
+
JAX device mesh supplied to the custom-partitioning rule.
|
|
162
|
+
arg_shapes
|
|
163
|
+
Abstract input shapes supplied by JAX custom partitioning.
|
|
164
|
+
result_shape
|
|
165
|
+
Abstract result shape supplied by JAX custom partitioning."""
|
|
166
|
+
arg_shardings = tree.tree_map(lambda x: x.sharding, arg_shapes)
|
|
167
|
+
return mesh, basic_fft, supported_sharding(arg_shardings[0], arg_shapes[0]), (
|
|
168
|
+
supported_sharding(arg_shardings[0], arg_shapes[0]),)
|
|
169
|
+
|
|
170
|
+
def infer_sharding_from_operands(mesh, arg_shapes, result_shape):
|
|
171
|
+
"""Infers the sharding of the output based on the input sharding.
|
|
172
|
+
|
|
173
|
+
Parameters
|
|
174
|
+
----------
|
|
175
|
+
mesh
|
|
176
|
+
JAX device mesh supplied to the custom-partitioning rule.
|
|
177
|
+
arg_shapes
|
|
178
|
+
Abstract input shapes supplied by JAX custom partitioning.
|
|
179
|
+
result_shape
|
|
180
|
+
Abstract result shape supplied by JAX custom partitioning."""
|
|
181
|
+
arg_shardings = tree.tree_map(lambda x: x.sharding, arg_shapes)
|
|
182
|
+
return supported_sharding(arg_shardings[0], arg_shapes[0])
|
|
183
|
+
|
|
184
|
+
sharded_fft.def_partition(
|
|
185
|
+
infer_sharding_from_operands=infer_sharding_from_operands,
|
|
186
|
+
partition=partition
|
|
187
|
+
)
|
|
188
|
+
return sharded_fft
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _fftn_first_pass(x):
|
|
192
|
+
""" Perform the FFT along the first two axes. """
|
|
193
|
+
return jnp.fft.fft(x, axis=0)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _fftn_second_pass(x):
|
|
197
|
+
""" Perform the FFT along the last axis using FFT due to symmetry. """
|
|
198
|
+
return jnp.fft.fftn(x, axes=[1, 2])
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _rfftn_second_pass(x):
|
|
202
|
+
""" Perform the real FFT along the last axis using real FFT due to symmetry. """
|
|
203
|
+
return jnp.fft.rfftn(x, axes=[1, 2])
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _ifftn_first_pass(x):
|
|
207
|
+
""" Perform the inverse FFT along the first two axes. """
|
|
208
|
+
return jnp.fft.ifft(x, axis=0)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _ifftn_second_pass(x):
|
|
212
|
+
""" Perform the inverse FFT along the last axis using FFT due to symmetry. """
|
|
213
|
+
return jnp.fft.ifftn(x, axes=[1, 2])
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _irfftn_second_pass(x):
|
|
217
|
+
""" Perform the real inverse FFT along the last axis using real FFT due to symmetry. """
|
|
218
|
+
return jnp.fft.irfftn(x, axes=[1, 2])
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _batched_fftn_first_pass(x):
|
|
222
|
+
"""Perform the batched FFT along the distributed x-axis."""
|
|
223
|
+
return jnp.fft.fft(x, axis=1)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _batched_rfftn_second_pass(x):
|
|
227
|
+
"""Perform the batched real FFT on the local y/z planes."""
|
|
228
|
+
return jnp.fft.rfftn(x, axes=[2, 3])
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _batched_ifftn_first_pass(x):
|
|
232
|
+
"""Perform the batched inverse FFT along the distributed x-axis."""
|
|
233
|
+
return jnp.fft.ifft(x, axis=1)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _batched_irfftn_second_pass(x):
|
|
237
|
+
"""Perform the batched real inverse FFT on the local y/z planes."""
|
|
238
|
+
return jnp.fft.irfftn(x, axes=[2, 3])
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def create_batched_transposed_real_ffts(compute_mesh: Mesh) -> Tuple[Callable, Callable]:
|
|
242
|
+
"""Create batched real FFT helpers for the transposed spectral layout.
|
|
243
|
+
|
|
244
|
+
Parameters
|
|
245
|
+
----------
|
|
246
|
+
compute_mesh : Mesh
|
|
247
|
+
Device mesh over which the transposed spectral layout is sharded.
|
|
248
|
+
|
|
249
|
+
Returns
|
|
250
|
+
-------
|
|
251
|
+
tuple[callable, callable]
|
|
252
|
+
Batched forward and inverse real FFT helpers for the transposed layout.
|
|
253
|
+
|
|
254
|
+
Notes
|
|
255
|
+
-----
|
|
256
|
+
The leading axis is treated as a pure batch dimension and remains
|
|
257
|
+
replicated.
|
|
258
|
+
"""
|
|
259
|
+
if compute_mesh.size == 1:
|
|
260
|
+
_batched_rfftn_transposed_jit = jax.jit(lambda x: _batched_fftn_first_pass(_batched_rfftn_second_pass(x)))
|
|
261
|
+
_batched_irfftn_transposed_jit = jax.jit(lambda x: _batched_irfftn_second_pass(_batched_ifftn_first_pass(x)))
|
|
262
|
+
|
|
263
|
+
@custom_vjp
|
|
264
|
+
def batched_irfftn_transposed(x):
|
|
265
|
+
"""Apply the batched transposed-layout inverse real FFT.
|
|
266
|
+
|
|
267
|
+
Parameters
|
|
268
|
+
----------
|
|
269
|
+
x
|
|
270
|
+
Input array transformed by this FFT wrapper.
|
|
271
|
+
"""
|
|
272
|
+
return _batched_irfftn_transposed_jit(x)
|
|
273
|
+
|
|
274
|
+
def batched_irfftn_transposed_fwd(x):
|
|
275
|
+
"""Save the batched transposed inverse FFT input for its custom VJP.
|
|
276
|
+
|
|
277
|
+
Parameters
|
|
278
|
+
----------
|
|
279
|
+
x
|
|
280
|
+
Input array transformed by this FFT wrapper.
|
|
281
|
+
"""
|
|
282
|
+
return _batched_irfftn_transposed_jit(x), x.shape
|
|
283
|
+
|
|
284
|
+
def batched_irfftn_transposed_bwd(res, g):
|
|
285
|
+
"""Apply the adjoint of the batched transposed inverse real FFT.
|
|
286
|
+
|
|
287
|
+
Parameters
|
|
288
|
+
----------
|
|
289
|
+
res
|
|
290
|
+
Residual values saved by a custom VJP forward rule.
|
|
291
|
+
g
|
|
292
|
+
Cotangent array supplied to a custom VJP backward rule.
|
|
293
|
+
"""
|
|
294
|
+
real_shape = g.shape
|
|
295
|
+
g = _batched_rfftn_transposed_jit(g).conj()
|
|
296
|
+
n = g.shape[-1]
|
|
297
|
+
is_even = (real_shape[-1] % 2 == 0)
|
|
298
|
+
|
|
299
|
+
mask = jnp.ones((n,), dtype=g.real.dtype)
|
|
300
|
+
if n > 1:
|
|
301
|
+
mask = mask.at[1:].set(2.0)
|
|
302
|
+
if is_even:
|
|
303
|
+
mask = mask.at[n - 1].set(1.0)
|
|
304
|
+
|
|
305
|
+
scale = jnp.asarray(1 / np.prod(real_shape[1:]), dtype=g.real.dtype)
|
|
306
|
+
out = scale * g * lax.expand_dims(mask, range(g.ndim - 1))
|
|
307
|
+
return (out,)
|
|
308
|
+
|
|
309
|
+
batched_irfftn_transposed.defvjp(
|
|
310
|
+
batched_irfftn_transposed_fwd,
|
|
311
|
+
batched_irfftn_transposed_bwd,
|
|
312
|
+
)
|
|
313
|
+
return _batched_rfftn_transposed_jit, batched_irfftn_transposed
|
|
314
|
+
|
|
315
|
+
batched_fftn_first_pass = create_sharded_fft(
|
|
316
|
+
_batched_fftn_first_pass,
|
|
317
|
+
P(None, None, "gpus", None),
|
|
318
|
+
compute_mesh,
|
|
319
|
+
)
|
|
320
|
+
batched_rfftn_second_pass = create_sharded_fft(
|
|
321
|
+
_batched_rfftn_second_pass,
|
|
322
|
+
P(None, "gpus", None, None),
|
|
323
|
+
compute_mesh,
|
|
324
|
+
)
|
|
325
|
+
batched_ifftn_first_pass = create_sharded_fft(
|
|
326
|
+
_batched_ifftn_first_pass,
|
|
327
|
+
P(None, None, "gpus", None),
|
|
328
|
+
compute_mesh,
|
|
329
|
+
)
|
|
330
|
+
batched_irfftn_second_pass = create_sharded_fft(
|
|
331
|
+
_batched_irfftn_second_pass,
|
|
332
|
+
P(None, "gpus", None, None),
|
|
333
|
+
compute_mesh,
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
def _batched_rfftn_transposed(x):
|
|
337
|
+
x = batched_rfftn_second_pass(x)
|
|
338
|
+
x = batched_fftn_first_pass(x)
|
|
339
|
+
return x
|
|
340
|
+
|
|
341
|
+
def _batched_irfftn_transposed(x):
|
|
342
|
+
x = batched_ifftn_first_pass(x)
|
|
343
|
+
x = batched_irfftn_second_pass(x)
|
|
344
|
+
return x
|
|
345
|
+
|
|
346
|
+
_batched_rfftn_transposed_jit = jax.jit(
|
|
347
|
+
_batched_rfftn_transposed,
|
|
348
|
+
in_shardings=(NamedSharding(compute_mesh, P(None, "gpus", None, None))),
|
|
349
|
+
out_shardings=(NamedSharding(compute_mesh, P(None, None, "gpus", None))),
|
|
350
|
+
)
|
|
351
|
+
_batched_irfftn_transposed_jit = jax.jit(
|
|
352
|
+
_batched_irfftn_transposed,
|
|
353
|
+
in_shardings=(NamedSharding(compute_mesh, P(None, None, "gpus", None))),
|
|
354
|
+
out_shardings=(NamedSharding(compute_mesh, P(None, "gpus", None, None))),
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
@custom_vjp
|
|
358
|
+
def batched_irfftn_transposed(x):
|
|
359
|
+
"""Apply the batched transposed-layout inverse real FFT.
|
|
360
|
+
|
|
361
|
+
Parameters
|
|
362
|
+
----------
|
|
363
|
+
x
|
|
364
|
+
Input array transformed by this FFT wrapper.
|
|
365
|
+
"""
|
|
366
|
+
return _batched_irfftn_transposed_jit(x)
|
|
367
|
+
|
|
368
|
+
def batched_irfftn_transposed_fwd(x):
|
|
369
|
+
"""Save the batched transposed inverse FFT input for its custom VJP.
|
|
370
|
+
|
|
371
|
+
Parameters
|
|
372
|
+
----------
|
|
373
|
+
x
|
|
374
|
+
Input array transformed by this FFT wrapper.
|
|
375
|
+
"""
|
|
376
|
+
return _batched_irfftn_transposed_jit(x), x.shape
|
|
377
|
+
|
|
378
|
+
def batched_irfftn_transposed_bwd(res, g):
|
|
379
|
+
"""Apply the adjoint of the batched transposed inverse real FFT.
|
|
380
|
+
|
|
381
|
+
Parameters
|
|
382
|
+
----------
|
|
383
|
+
res
|
|
384
|
+
Residual values saved by a custom VJP forward rule.
|
|
385
|
+
g
|
|
386
|
+
Cotangent array supplied to a custom VJP backward rule.
|
|
387
|
+
"""
|
|
388
|
+
real_shape = g.shape
|
|
389
|
+
g = _batched_rfftn_transposed_jit(g).conj()
|
|
390
|
+
n = g.shape[-1]
|
|
391
|
+
is_even = (real_shape[-1] % 2 == 0)
|
|
392
|
+
|
|
393
|
+
mask = jnp.ones((n,), dtype=g.real.dtype)
|
|
394
|
+
if n > 1:
|
|
395
|
+
mask = mask.at[1:].set(2.0)
|
|
396
|
+
if is_even:
|
|
397
|
+
mask = mask.at[n - 1].set(1.0)
|
|
398
|
+
|
|
399
|
+
scale = jnp.asarray(1 / np.prod(real_shape[1:]), dtype=g.real.dtype)
|
|
400
|
+
out = scale * g * lax.expand_dims(mask, range(g.ndim - 1))
|
|
401
|
+
return (out,)
|
|
402
|
+
|
|
403
|
+
batched_irfftn_transposed.defvjp(
|
|
404
|
+
batched_irfftn_transposed_fwd,
|
|
405
|
+
batched_irfftn_transposed_bwd,
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
return _batched_rfftn_transposed_jit, batched_irfftn_transposed
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def create_ffts(compute_mesh: Mesh) -> Tuple[Callable, Callable, Callable, Callable, Callable, Callable]:
|
|
412
|
+
"""Create the distributed FFT helper family used by PM++.
|
|
413
|
+
|
|
414
|
+
Parameters
|
|
415
|
+
----------
|
|
416
|
+
compute_mesh : Mesh
|
|
417
|
+
Device mesh defining the parallel FFT layout.
|
|
418
|
+
|
|
419
|
+
Returns
|
|
420
|
+
-------
|
|
421
|
+
tuple[callable, callable, callable, callable, callable, callable]
|
|
422
|
+
``(rfftn, irfftn, fftn, ifftn, rfftn_transposed, irfftn_transposed)``
|
|
423
|
+
helpers specialized to ``compute_mesh``.
|
|
424
|
+
|
|
425
|
+
Notes
|
|
426
|
+
-----
|
|
427
|
+
The real transforms carry custom VJPs so they remain differentiable in the
|
|
428
|
+
PM++ solver. The complex transforms are utility kernels without the same
|
|
429
|
+
public differentiation contract.
|
|
430
|
+
"""
|
|
431
|
+
# Single-device shortcut: bypass distributed 2-pass FFT entirely.
|
|
432
|
+
# The strided-batched cuFFT plan created by the first pass is incompatible
|
|
433
|
+
# with some single-GPU hardware (e.g. GTX 1650 / cuFFT 12).
|
|
434
|
+
if compute_mesh.size == 1:
|
|
435
|
+
_fftn_jit = jax.jit(jnp.fft.fftn)
|
|
436
|
+
_ifftn_jit = jax.jit(jnp.fft.ifftn)
|
|
437
|
+
_rfftn_jit = jax.jit(jnp.fft.rfftn)
|
|
438
|
+
_irfftn_jit = jax.jit(jnp.fft.irfftn)
|
|
439
|
+
|
|
440
|
+
@custom_vjp
|
|
441
|
+
def rfftn(x):
|
|
442
|
+
"""Apply the distributed real-to-complex FFT wrapper.
|
|
443
|
+
|
|
444
|
+
Parameters
|
|
445
|
+
----------
|
|
446
|
+
x
|
|
447
|
+
Input array transformed by this FFT wrapper.
|
|
448
|
+
"""
|
|
449
|
+
return _rfftn_jit(x)
|
|
450
|
+
|
|
451
|
+
def rfftn_fwd(x):
|
|
452
|
+
"""Save the real FFT input shape for its custom VJP.
|
|
453
|
+
|
|
454
|
+
Parameters
|
|
455
|
+
----------
|
|
456
|
+
x
|
|
457
|
+
Input array transformed by this FFT wrapper.
|
|
458
|
+
"""
|
|
459
|
+
return _rfftn_jit(x), x.shape
|
|
460
|
+
|
|
461
|
+
def rfftn_bwd(x_shape, g):
|
|
462
|
+
"""Apply the inverse transform used by the real FFT custom VJP.
|
|
463
|
+
|
|
464
|
+
Parameters
|
|
465
|
+
----------
|
|
466
|
+
x_shape
|
|
467
|
+
Original real-space input shape saved for an inverse custom VJP.
|
|
468
|
+
g
|
|
469
|
+
Cotangent array supplied to a custom VJP backward rule.
|
|
470
|
+
"""
|
|
471
|
+
g = jnp.pad(g, [(0, si - xi) for xi, si in zip(g.shape, x_shape)])
|
|
472
|
+
g = _ifftn_jit(g.conj()).real
|
|
473
|
+
g *= jnp.prod(jnp.array(x_shape))
|
|
474
|
+
return (g,)
|
|
475
|
+
|
|
476
|
+
rfftn.defvjp(rfftn_fwd, rfftn_bwd)
|
|
477
|
+
|
|
478
|
+
@custom_vjp
|
|
479
|
+
def irfftn(x):
|
|
480
|
+
"""Apply the distributed complex-to-real inverse FFT wrapper.
|
|
481
|
+
|
|
482
|
+
Parameters
|
|
483
|
+
----------
|
|
484
|
+
x
|
|
485
|
+
Input array transformed by this FFT wrapper.
|
|
486
|
+
"""
|
|
487
|
+
return _irfftn_jit(x)
|
|
488
|
+
|
|
489
|
+
def irfftn_fwd(x):
|
|
490
|
+
"""Save the inverse real FFT input for its custom VJP.
|
|
491
|
+
|
|
492
|
+
Parameters
|
|
493
|
+
----------
|
|
494
|
+
x
|
|
495
|
+
Input array transformed by this FFT wrapper.
|
|
496
|
+
"""
|
|
497
|
+
return _irfftn_jit(x), x.shape
|
|
498
|
+
|
|
499
|
+
def irfftn_bwd(res, g):
|
|
500
|
+
"""Apply the real FFT adjoint for the inverse real FFT custom VJP.
|
|
501
|
+
|
|
502
|
+
Parameters
|
|
503
|
+
----------
|
|
504
|
+
res
|
|
505
|
+
Residual values saved by a custom VJP forward rule.
|
|
506
|
+
g
|
|
507
|
+
Cotangent array supplied to a custom VJP backward rule.
|
|
508
|
+
"""
|
|
509
|
+
real_shape = g.shape
|
|
510
|
+
g = _rfftn_jit(g).conj()
|
|
511
|
+
n = g.shape[-1]
|
|
512
|
+
is_even = (real_shape[-1] % 2 == 0)
|
|
513
|
+
mask = jnp.ones((n,), dtype=g.real.dtype)
|
|
514
|
+
if n > 1:
|
|
515
|
+
mask = mask.at[1:].set(2.0)
|
|
516
|
+
if is_even:
|
|
517
|
+
mask = mask.at[n - 1].set(1.0)
|
|
518
|
+
scale = jnp.asarray(1 / np.prod(real_shape), dtype=g.real.dtype)
|
|
519
|
+
out = scale * g * lax.expand_dims(mask, range(g.ndim - 1))
|
|
520
|
+
return (out,)
|
|
521
|
+
|
|
522
|
+
irfftn.defvjp(irfftn_fwd, irfftn_bwd)
|
|
523
|
+
|
|
524
|
+
return rfftn, irfftn, _fftn_jit, _ifftn_jit, rfftn, irfftn
|
|
525
|
+
|
|
526
|
+
# Creating sharded versions of FFT and IFFT functions for specific GPU sharding
|
|
527
|
+
fftn_first_pass = create_sharded_fft(_fftn_first_pass, P(None, "gpus", None), compute_mesh)
|
|
528
|
+
fftn_second_pass = create_sharded_fft(_fftn_second_pass, P("gpus", None, None), compute_mesh)
|
|
529
|
+
rfftn_second_pass = create_sharded_fft(_rfftn_second_pass, P("gpus", None, None), compute_mesh)
|
|
530
|
+
ifftn_first_pass = create_sharded_fft(_ifftn_first_pass, P(None, "gpus", None), compute_mesh)
|
|
531
|
+
ifftn_second_pass = create_sharded_fft(_ifftn_second_pass, P("gpus", None, None), compute_mesh)
|
|
532
|
+
irfftn_second_pass = create_sharded_fft(_irfftn_second_pass, P("gpus", None, None), compute_mesh)
|
|
533
|
+
|
|
534
|
+
def _fftn(x):
|
|
535
|
+
""" Perform a forward FFT. """
|
|
536
|
+
x = fftn_second_pass(x)
|
|
537
|
+
x = fftn_first_pass(x)
|
|
538
|
+
return x
|
|
539
|
+
|
|
540
|
+
def _ifftn(x):
|
|
541
|
+
""" Perform an inverse FFT. """
|
|
542
|
+
x = ifftn_first_pass(x)
|
|
543
|
+
x = ifftn_second_pass(x)
|
|
544
|
+
return x
|
|
545
|
+
|
|
546
|
+
def _rfftn(x):
|
|
547
|
+
""" Perform a real-valued forward FFT. """
|
|
548
|
+
x = rfftn_second_pass(x)
|
|
549
|
+
x = fftn_first_pass(x)
|
|
550
|
+
return x
|
|
551
|
+
|
|
552
|
+
def _irfftn(x):
|
|
553
|
+
""" Perform a real-valued inverse FFT. """
|
|
554
|
+
x = ifftn_first_pass(x)
|
|
555
|
+
x = irfftn_second_pass(x)
|
|
556
|
+
return x
|
|
557
|
+
|
|
558
|
+
# Creating jitted versions of FFT and IFFT functions for specific GPU sharding
|
|
559
|
+
_rfftn_jit = jax.jit(_rfftn, in_shardings=(NamedSharding(compute_mesh, P("gpus", None, None))),
|
|
560
|
+
out_shardings=(NamedSharding(compute_mesh, P("gpus", None, None))))
|
|
561
|
+
_irfftn_jit = jax.jit(_irfftn, in_shardings=(NamedSharding(compute_mesh, P("gpus", None, None))),
|
|
562
|
+
out_shardings=(NamedSharding(compute_mesh, P("gpus", None, None))))
|
|
563
|
+
_fftn_jit = jax.jit(_fftn, in_shardings=(NamedSharding(compute_mesh, P("gpus", None, None))),
|
|
564
|
+
out_shardings=(NamedSharding(compute_mesh, P("gpus", None, None))))
|
|
565
|
+
_ifftn_jit = jax.jit(_ifftn, in_shardings=(NamedSharding(compute_mesh, P("gpus", None, None))),
|
|
566
|
+
out_shardings=(NamedSharding(compute_mesh, P("gpus", None, None))))
|
|
567
|
+
# Natural spectral layout: keep the intermediate transpose produced by the
|
|
568
|
+
# x-axis FFT so downstream spectral operators can avoid bouncing layouts.
|
|
569
|
+
_rfftn_transposed_jit = jax.jit(
|
|
570
|
+
_rfftn,
|
|
571
|
+
in_shardings=(NamedSharding(compute_mesh, P("gpus", None, None))),
|
|
572
|
+
out_shardings=(NamedSharding(compute_mesh, P(None, "gpus", None))),
|
|
573
|
+
)
|
|
574
|
+
_irfftn_transposed_jit = jax.jit(
|
|
575
|
+
_irfftn,
|
|
576
|
+
in_shardings=(NamedSharding(compute_mesh, P(None, "gpus", None))),
|
|
577
|
+
out_shardings=(NamedSharding(compute_mesh, P("gpus", None, None))),
|
|
578
|
+
)
|
|
579
|
+
_fftn_transposed_jit = jax.jit(
|
|
580
|
+
_fftn,
|
|
581
|
+
in_shardings=(NamedSharding(compute_mesh, P("gpus", None, None))),
|
|
582
|
+
out_shardings=(NamedSharding(compute_mesh, P(None, "gpus", None))),
|
|
583
|
+
)
|
|
584
|
+
_ifftn_transposed_jit = jax.jit(
|
|
585
|
+
_ifftn,
|
|
586
|
+
in_shardings=(NamedSharding(compute_mesh, P(None, "gpus", None))),
|
|
587
|
+
out_shardings=(NamedSharding(compute_mesh, P("gpus", None, None))),
|
|
588
|
+
)
|
|
589
|
+
|
|
590
|
+
@custom_vjp
|
|
591
|
+
def rfftn(x):
|
|
592
|
+
"""Perform a real-valued forward FFT with custom VJP (vector-Jacobian product) defined.
|
|
593
|
+
|
|
594
|
+
Note:
|
|
595
|
+
This function is differentiable and the derivative is defined using VJP.
|
|
596
|
+
|
|
597
|
+
Parameters
|
|
598
|
+
----------
|
|
599
|
+
x
|
|
600
|
+
Input array transformed by this FFT wrapper."""
|
|
601
|
+
return _rfftn_jit(x)
|
|
602
|
+
|
|
603
|
+
def rfftn_fwd(x):
|
|
604
|
+
"""Forward pass for custom VJP of real-valued forward FFT
|
|
605
|
+
|
|
606
|
+
Parameters
|
|
607
|
+
----------
|
|
608
|
+
x
|
|
609
|
+
Input array transformed by this FFT wrapper."""
|
|
610
|
+
return _rfftn_jit(x), x.shape
|
|
611
|
+
|
|
612
|
+
def rfftn_bwd(x_shape, g):
|
|
613
|
+
"""Backward pass for custom VJP of real-valued forward FFT
|
|
614
|
+
|
|
615
|
+
Parameters
|
|
616
|
+
----------
|
|
617
|
+
x_shape
|
|
618
|
+
Original real-space input shape saved for an inverse custom VJP.
|
|
619
|
+
g
|
|
620
|
+
Cotangent array supplied to a custom VJP backward rule."""
|
|
621
|
+
g = jnp.pad(g, [(0, si - xi) for xi, si in zip(g.shape, x_shape)])
|
|
622
|
+
g = _ifftn_jit(g.conj()).real
|
|
623
|
+
# the previous code is equivalent to jnp.fft.ifftn(g.conj(), s=x_shape).real
|
|
624
|
+
g *= jnp.prod(jnp.array(x_shape))
|
|
625
|
+
return (g,)
|
|
626
|
+
|
|
627
|
+
rfftn.defvjp(rfftn_fwd, rfftn_bwd)
|
|
628
|
+
|
|
629
|
+
@custom_vjp
|
|
630
|
+
def rfftn_transposed(x):
|
|
631
|
+
"""Apply the distributed real FFT and return transposed spectral layout.
|
|
632
|
+
|
|
633
|
+
Parameters
|
|
634
|
+
----------
|
|
635
|
+
x
|
|
636
|
+
Input array transformed by this FFT wrapper.
|
|
637
|
+
"""
|
|
638
|
+
return _rfftn_transposed_jit(x)
|
|
639
|
+
|
|
640
|
+
def rfftn_transposed_fwd(x):
|
|
641
|
+
"""Save the transposed real FFT input shape for its custom VJP.
|
|
642
|
+
|
|
643
|
+
Parameters
|
|
644
|
+
----------
|
|
645
|
+
x
|
|
646
|
+
Input array transformed by this FFT wrapper.
|
|
647
|
+
"""
|
|
648
|
+
return _rfftn_transposed_jit(x), x.shape
|
|
649
|
+
|
|
650
|
+
def rfftn_transposed_bwd(x_shape, g):
|
|
651
|
+
"""Apply the adjoint of the transposed real FFT.
|
|
652
|
+
|
|
653
|
+
Parameters
|
|
654
|
+
----------
|
|
655
|
+
x_shape
|
|
656
|
+
Original real-space input shape saved for an inverse custom VJP.
|
|
657
|
+
g
|
|
658
|
+
Cotangent array supplied to a custom VJP backward rule.
|
|
659
|
+
"""
|
|
660
|
+
g = jnp.pad(g, [(0, si - xi) for xi, si in zip(g.shape, x_shape)])
|
|
661
|
+
g = _ifftn_transposed_jit(g.conj()).real
|
|
662
|
+
g *= jnp.prod(jnp.array(x_shape))
|
|
663
|
+
return (g,)
|
|
664
|
+
|
|
665
|
+
rfftn_transposed.defvjp(rfftn_transposed_fwd, rfftn_transposed_bwd)
|
|
666
|
+
|
|
667
|
+
@custom_vjp
|
|
668
|
+
def irfftn(x):
|
|
669
|
+
"""Perform a real-valued inverse FFT with custom VJP (vector-Jacobian product) defined.
|
|
670
|
+
|
|
671
|
+
Note:
|
|
672
|
+
This function is differentiable and the derivative is defined using VJP.
|
|
673
|
+
|
|
674
|
+
Parameters
|
|
675
|
+
----------
|
|
676
|
+
x
|
|
677
|
+
Input array transformed by this FFT wrapper."""
|
|
678
|
+
return _irfftn_jit(x)
|
|
679
|
+
|
|
680
|
+
def irfftn_fwd(x):
|
|
681
|
+
"""Forward pass for custom VJP of real-valued inverse FFT
|
|
682
|
+
|
|
683
|
+
Parameters
|
|
684
|
+
----------
|
|
685
|
+
x
|
|
686
|
+
Input array transformed by this FFT wrapper."""
|
|
687
|
+
return _irfftn_jit(x), x.shape
|
|
688
|
+
|
|
689
|
+
def irfftn_bwd(res, g):
|
|
690
|
+
"""Backward pass for custom VJP of real-valued inverse FFT
|
|
691
|
+
|
|
692
|
+
Parameters
|
|
693
|
+
----------
|
|
694
|
+
res
|
|
695
|
+
Residual values saved by a custom VJP forward rule.
|
|
696
|
+
g
|
|
697
|
+
Cotangent array supplied to a custom VJP backward rule."""
|
|
698
|
+
real_shape = g.shape
|
|
699
|
+
# Compute the RFFT of the gradient (changes the size of g)
|
|
700
|
+
g = _rfftn_jit(g).conj() # jnp.fft.rfftn(g) # _rfftn_jit(g)
|
|
701
|
+
# Apply the scaling factor and mask
|
|
702
|
+
n = g.shape[-1]
|
|
703
|
+
is_even = (real_shape[-1] % 2 == 0)
|
|
704
|
+
|
|
705
|
+
# The FFT shape is static under JIT, so the Hermitian weighting can be built
|
|
706
|
+
# directly without padding to an external max size.
|
|
707
|
+
mask = jnp.ones((n,), dtype=g.real.dtype)
|
|
708
|
+
if n > 1:
|
|
709
|
+
mask = mask.at[1:].set(2.0)
|
|
710
|
+
if is_even:
|
|
711
|
+
mask = mask.at[n - 1].set(1.0)
|
|
712
|
+
|
|
713
|
+
scale = jnp.asarray(1 / np.prod(real_shape), dtype=g.real.dtype)
|
|
714
|
+
out = scale * g * lax.expand_dims(mask, range(g.ndim - 1))
|
|
715
|
+
return (out,)
|
|
716
|
+
|
|
717
|
+
irfftn.defvjp(irfftn_fwd, irfftn_bwd)
|
|
718
|
+
|
|
719
|
+
@custom_vjp
|
|
720
|
+
def irfftn_transposed(x):
|
|
721
|
+
"""Apply the inverse real FFT from transposed spectral layout.
|
|
722
|
+
|
|
723
|
+
Parameters
|
|
724
|
+
----------
|
|
725
|
+
x
|
|
726
|
+
Input array transformed by this FFT wrapper.
|
|
727
|
+
"""
|
|
728
|
+
return _irfftn_transposed_jit(x)
|
|
729
|
+
|
|
730
|
+
def irfftn_transposed_fwd(x):
|
|
731
|
+
"""Save the transposed inverse FFT input for its custom VJP.
|
|
732
|
+
|
|
733
|
+
Parameters
|
|
734
|
+
----------
|
|
735
|
+
x
|
|
736
|
+
Input array transformed by this FFT wrapper.
|
|
737
|
+
"""
|
|
738
|
+
return _irfftn_transposed_jit(x), x.shape
|
|
739
|
+
|
|
740
|
+
def irfftn_transposed_bwd(res, g):
|
|
741
|
+
"""Apply the adjoint of the transposed inverse real FFT.
|
|
742
|
+
|
|
743
|
+
Parameters
|
|
744
|
+
----------
|
|
745
|
+
res
|
|
746
|
+
Residual values saved by a custom VJP forward rule.
|
|
747
|
+
g
|
|
748
|
+
Cotangent array supplied to a custom VJP backward rule.
|
|
749
|
+
"""
|
|
750
|
+
real_shape = g.shape
|
|
751
|
+
g = _rfftn_transposed_jit(g).conj()
|
|
752
|
+
n = g.shape[-1]
|
|
753
|
+
is_even = (real_shape[-1] % 2 == 0)
|
|
754
|
+
|
|
755
|
+
mask = jnp.ones((n,), dtype=g.real.dtype)
|
|
756
|
+
if n > 1:
|
|
757
|
+
mask = mask.at[1:].set(2.0)
|
|
758
|
+
if is_even:
|
|
759
|
+
mask = mask.at[n - 1].set(1.0)
|
|
760
|
+
|
|
761
|
+
scale = jnp.asarray(1 / np.prod(real_shape), dtype=g.real.dtype)
|
|
762
|
+
out = scale * g * lax.expand_dims(mask, range(g.ndim - 1))
|
|
763
|
+
return (out,)
|
|
764
|
+
|
|
765
|
+
irfftn_transposed.defvjp(irfftn_transposed_fwd, irfftn_transposed_bwd)
|
|
766
|
+
|
|
767
|
+
return rfftn, irfftn, _fftn_jit, _ifftn_jit, rfftn_transposed, irfftn_transposed
|
|
768
|
+
|
|
769
|
+
|
|
770
|
+
def test_functions(distributed_func_jit: Callable, reference_func: Callable, array: np.ndarray, mesh: Mesh) -> Tuple[
|
|
771
|
+
bool, float, float]:
|
|
772
|
+
"""
|
|
773
|
+
Tests the accuracy and computational performance of two functions. It does this by comparing the output of
|
|
774
|
+
a distributed function (that's been Just-in-Time compiled) provided as "distributed_func_jit" and a reference function.
|
|
775
|
+
It reports whether the outputs of both functions are close (within a certain tolerance), the maximum absolute difference
|
|
776
|
+
in their outputs, and the value at which the maximum absolute difference occurs in the reference function output.
|
|
777
|
+
|
|
778
|
+
Args:
|
|
779
|
+
distributed_func_jit (Callable): A function that distributes its computations across multiple devices and has
|
|
780
|
+
been Just-In-Time compiled. This is the function being tested.
|
|
781
|
+
reference_func (Callable): A reference function that is used to compare the accuracy and performance of the
|
|
782
|
+
distributed function.
|
|
783
|
+
array (np.ndarray): The input numpy array that both functions will use to compute their outputs.
|
|
784
|
+
mesh (Mesh) : The compute mesh that defines the layout of devices for distributed computation.
|
|
785
|
+
|
|
786
|
+
Returns:
|
|
787
|
+
tuple: A tuple containing the following elements:
|
|
788
|
+
- all_close (bool): A boolean indicating if the outputs of both functions are close within a relative tolerance of
|
|
789
|
+
1.e-2 and absolute tolerance of 1.e-4.
|
|
790
|
+
- max_diff (float): The maximum absolute difference in the outputs of both functions.
|
|
791
|
+
- max_diff_value_ref_func (float): The value in the output of the reference function at which the maximum absolute
|
|
792
|
+
difference occurs.
|
|
793
|
+
|
|
794
|
+
Note:
|
|
795
|
+
If the maximum absolute difference is non-zero, also prints the maximum relative difference and the corresponding value
|
|
796
|
+
in the output of the reference function.
|
|
797
|
+
"""
|
|
798
|
+
print(f"\nTests for function: {reference_func.__name__}.\n")
|
|
799
|
+
array_distributed = distribute_array_on_gpus(array, mesh, P(None, "gpus", None))
|
|
800
|
+
reference_func_jit = jax.jit(reference_func)
|
|
801
|
+
with mesh:
|
|
802
|
+
distributed_func_jit_out = distributed_func_jit(array_distributed)
|
|
803
|
+
reference_func_jit_out = reference_func_jit(array)
|
|
804
|
+
|
|
805
|
+
all_close = np.allclose(distributed_func_jit_out, reference_func_jit_out, rtol=1.e-2, atol=1.e-4)
|
|
806
|
+
diff = reference_func_jit_out - distributed_func_jit_out
|
|
807
|
+
max_diff = np.max(np.abs(diff))
|
|
808
|
+
max_diff_index = np.unravel_index(np.argmax(np.abs(diff)), diff.shape)
|
|
809
|
+
max_diff_value_ref = reference_func_jit_out[max_diff_index]
|
|
810
|
+
|
|
811
|
+
print(f"\n{'Output close to reference' if all_close else 'WARNING: Output not close to reference'}")
|
|
812
|
+
print(f"{max_diff = }")
|
|
813
|
+
print(f"{max_diff_value_ref = }")
|
|
814
|
+
|
|
815
|
+
if max_diff != 0:
|
|
816
|
+
relative_diff = np.abs(diff) / np.maximum(np.abs(distributed_func_jit_out), np.abs(reference_func_jit_out))
|
|
817
|
+
max_relative_diff = np.max(relative_diff)
|
|
818
|
+
max_relative_diff_index = np.unravel_index(np.argmax(relative_diff), relative_diff.shape)
|
|
819
|
+
max_relative_diff_value_ref = reference_func_jit_out[max_relative_diff_index]
|
|
820
|
+
|
|
821
|
+
print(f"{max_relative_diff = }")
|
|
822
|
+
print(f"{max_relative_diff_value_ref = }")
|
|
823
|
+
|
|
824
|
+
|
|
825
|
+
if __name__ == '__main__':
|
|
826
|
+
import os
|
|
827
|
+
|
|
828
|
+
num_gpus = jax.device_count()
|
|
829
|
+
print("CUDA_VISIBLE_DEVICES", os.environ.get("CUDA_VISIBLE_DEVICES"))
|
|
830
|
+
print("devices:", jax.device_count(), jax.devices())
|
|
831
|
+
print("local_devices:", jax.local_device_count(), jax.local_devices())
|
|
832
|
+
print("process_index", jax.process_index())
|
|
833
|
+
print("total number of GPUs:", num_gpus)
|
|
834
|
+
|
|
835
|
+
devices = mesh_utils.create_device_mesh((num_gpus,), devices=jax.devices()[:num_gpus])
|
|
836
|
+
mesh = Mesh(devices, axis_names=("gpus",))
|
|
837
|
+
|
|
838
|
+
rfftn_jit, irfftn_jit, fftn_jit, ifftn_jit = create_ffts(mesh)
|
|
839
|
+
|
|
840
|
+
jax.config.update("jax_enable_x64", True)
|
|
841
|
+
|
|
842
|
+
size = 512
|
|
843
|
+
global_shape = (size, size, size)
|
|
844
|
+
x = np.random.default_rng(2).random(global_shape, dtype=np.float64)
|
|
845
|
+
|
|
846
|
+
x_distributed = distribute_array_on_gpus(x, mesh, P(None, "gpus", None))
|
|
847
|
+
|
|
848
|
+
with mesh:
|
|
849
|
+
rfftn_out = rfftn_jit(x_distributed)
|
|
850
|
+
|
|
851
|
+
test_functions(rfftn_jit, jnp.fft.rfftn, x, mesh)
|