mirror of
https://github.com/cdtools-developers/cdtools.git
synced 2026-09-11 22:12:38 +02:00
172 lines
6.2 KiB
Python
172 lines
6.2 KiB
Python
import cdtools
|
|
from cdtools.tools import multigpu
|
|
import pytest
|
|
import os
|
|
import subprocess
|
|
import torch as t
|
|
|
|
"""
|
|
This file contains several tests that are relevant to running multi-GPU
|
|
operations in CDTools.
|
|
"""
|
|
|
|
|
|
def reconstruct(rank, world_size, conn):
|
|
"""
|
|
An example reconstruction script to test the performance of 1 vs 2 GPU
|
|
operation.
|
|
"""
|
|
filename = os.environ.get('CDTOOLS_TESTING_GOLD_BALL_PATH')
|
|
cdtools.tools.multigpu.setup(rank=rank,
|
|
world_size=world_size)
|
|
dataset = cdtools.datasets.Ptycho2DDataset.from_cxi(filename)
|
|
|
|
pad = 10
|
|
dataset.pad(pad)
|
|
dataset.inspect()
|
|
model = cdtools.models.FancyPtycho.from_dataset(
|
|
dataset,
|
|
n_modes=3,
|
|
probe_support_radius=50,
|
|
propagation_distance=2e-6,
|
|
units='um',
|
|
probe_fourier_crop=pad
|
|
)
|
|
model.translation_offsets.data += 0.7 * \
|
|
t.randn_like(model.translation_offsets)
|
|
model.weights.requires_grad = False
|
|
|
|
device = 'cuda'
|
|
model.to(device=device)
|
|
dataset.get_as(device=device)
|
|
|
|
recon = cdtools.reconstructors.AdamReconstructor(model,
|
|
dataset,
|
|
rank=rank,
|
|
world_size=world_size)
|
|
|
|
for loss in recon.optimize(10, lr=0.005, batch_size=50):
|
|
if rank == 0 and model.epoch == 10:
|
|
print(model.report())
|
|
conn.send((model.loss_times, model.loss_history))
|
|
cdtools.tools.multigpu.cleanup()
|
|
|
|
|
|
@pytest.mark.multigpu
|
|
def test_plotting_saving_torchrun(lab_ptycho_cxi,
|
|
multigpu_script,
|
|
tmp_path,
|
|
show_plot):
|
|
"""
|
|
Run a multi-GPU test via torchrun on a script that executes several
|
|
plotting and file-saving methods from CDIModel and ensure they run
|
|
without failure.
|
|
|
|
Also, make sure that only 1 GPU is generating the plots.
|
|
|
|
If this test fails, one of three things happened:
|
|
1) Either something failed while multigpu_script_2 was called
|
|
2) Somehow, something aside from Rank 0 saved results
|
|
3) multigpu_script_2 was not able to save all the data files
|
|
we asked it to save.
|
|
"""
|
|
# Run the test script, which generates several files that either have
|
|
# the prefix
|
|
cmd = ['torchrun',
|
|
'--standalone',
|
|
'--nnodes=1',
|
|
'--nproc_per_node=2',
|
|
multigpu_script]
|
|
|
|
child_env = os.environ.copy()
|
|
child_env['CDTOOLS_TESTING_DATA_PATH'] = lab_ptycho_cxi
|
|
child_env['CDTOOLS_TESTING_TMP_PATH'] = str(tmp_path)
|
|
child_env['CDTOOLS_TESTING_SHOW_PLOT'] = str(int(show_plot))
|
|
|
|
try:
|
|
subprocess.run(cmd, check=True, env=child_env)
|
|
except subprocess.CalledProcessError:
|
|
# The called script is designed to throw an exception.
|
|
# TODO: Figure out how to distinguish between the engineered error
|
|
# in the script versus any other error.
|
|
pass
|
|
|
|
# Check if all the generated file names only have the prefix 'RANK_0'
|
|
filelist = [f for f in os.listdir(tmp_path)
|
|
if os.path.isfile(os.path.join(tmp_path, f))]
|
|
|
|
assert all([file.startswith('RANK_0') for file in filelist])
|
|
print('All files have the RANK_0 prefix.')
|
|
|
|
# Check if plots have been saved
|
|
if show_plot:
|
|
print('Plots generated: ' +
|
|
f"{sum([file.startswith('RANK_0_test_plot') for file in filelist])}") # noqa
|
|
assert any([file.startswith('RANK_0_test_plot') for file in filelist])
|
|
else:
|
|
print('--plot not enabled. Checks on plotting and figure saving' +
|
|
' will not be conducted.')
|
|
|
|
# Check if we have all five data files saved
|
|
file_output_suffix = ('test_save_checkpoint.pt',
|
|
'test_save_on_exit.h5',
|
|
'test_save_on_except.h5',
|
|
'test_save_to.h5',
|
|
'test_to_cxi.h5')
|
|
|
|
print(f'{sum([file.endswith(file_output_suffix) for file in filelist])}'
|
|
+ ' out of 5 data files have been generated.')
|
|
assert sum([file.endswith(file_output_suffix) for file in filelist]) \
|
|
== len(file_output_suffix)
|
|
|
|
|
|
@pytest.mark.multigpu
|
|
def test_reconstruction_quality_spawn(gold_ball_cxi,
|
|
show_plot):
|
|
"""
|
|
Run a multi-GPU speed test based on gold_ball_ptycho_speedtest.py
|
|
and make sure the final reconstructed loss using 2 GPUs is similar
|
|
to 1 GPU.
|
|
|
|
This test requires us to have 2 NVIDIA GPUs and makes use of the
|
|
multi-GPU speed test.
|
|
|
|
If this test fails, it indicates that the reconstruction quality is
|
|
getting noticably worse with increased GPU counts. This may be a symptom
|
|
of a synchronization/broadcasting issue between the different GPUs.
|
|
"""
|
|
# Make the gold_ball_cxi file path visible to the reconstruct function
|
|
os.environ['CDTOOLS_TESTING_GOLD_BALL_PATH'] = gold_ball_cxi
|
|
|
|
loss_mean_list, loss_std_list, \
|
|
_, _, speed_up_mean_list, speed_up_std_list\
|
|
= multigpu.run_speed_test(fn=reconstruct,
|
|
gpu_counts=(1, 2),
|
|
runs=3,
|
|
show_plot=show_plot)
|
|
|
|
# Make sure that the final loss values between the 1 and 2 GPU tests
|
|
# are comprable to within 1 std of each other.
|
|
single_gpu_loss_mean = loss_mean_list[0][-1]
|
|
single_gpu_loss_std = loss_std_list[0][-1]
|
|
double_gpu_loss_mean = loss_mean_list[1][-1]
|
|
double_gpu_loss_std = loss_std_list[1][-1]
|
|
|
|
single_gpu_loss_min = single_gpu_loss_mean - single_gpu_loss_std
|
|
single_gpu_loss_max = single_gpu_loss_mean + single_gpu_loss_std
|
|
multi_gpu_loss_min = double_gpu_loss_mean - double_gpu_loss_std
|
|
multi_gpu_loss_max = double_gpu_loss_mean + double_gpu_loss_std
|
|
|
|
has_loss_overlap = \
|
|
min(single_gpu_loss_max, multi_gpu_loss_max)\
|
|
> max(single_gpu_loss_min, multi_gpu_loss_min)
|
|
|
|
assert has_loss_overlap
|
|
|
|
# Make sure the loss mean falls below 3.2e-4. The values of losses I
|
|
# recorded at the time of testing were <3.19 e-4.
|
|
assert double_gpu_loss_mean < 3.2e-4
|
|
|
|
# Make sure that we have some speed up...
|
|
assert speed_up_mean_list[0] < speed_up_mean_list[1]
|