(fix:cuda) CUDA portability improved\nbuild system now checks for valid compiler compatible with nvcc and fails if that is not found and cuda is selected. Further, compiler choices are propegated throughout build along with CFLAGS and CXXFLAGS

This commit is contained in:
2026-09-07 10:51:34 -04:00
parent 323555b722
commit 7c99debf2f
6 changed files with 365 additions and 36 deletions

View File

@@ -161,3 +161,9 @@ if get_option('build_tests') and get_option('build_python')
timeout: 120,
)
endif
if get_option('build_tests')
test('cuda-toolchain-diagnostics', python_build,
args: files('test_cuda_toolchain.py'),
)
endif

View File

@@ -0,0 +1,88 @@
"""CUDA diagnostics and compiler propagation, without requiring a toolkit/GPU."""
import os
from pathlib import Path
import subprocess
import sys
import tempfile
from types import SimpleNamespace
import unittest
from unittest.mock import patch
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'tools'))
from check_cuda_toolchain import check_toolchain
from build_mfem_bundle import mpich_cuda_architectures, prepare_hypre_source
class CudaToolchainTest(unittest.TestCase):
def test_probe_checks_both_compilers_without_running_gpu_code(self):
calls = []
def run(command, **kwargs):
calls.append(command)
if '--version' not in command:
self.assertNotIn('NVCC_APPEND_FLAGS', kwargs['env'])
self.assertNotIn('NVCC_CCBIN', kwargs['env'])
return subprocess.CompletedProcess(command, 0, 'CUDA test toolkit', '')
with patch.dict(os.environ, {'NVCC_APPEND_FLAGS': '-allow-unsupported-compiler',
'NVCC_CCBIN': 'wrong-host'}), patch('subprocess.run', side_effect=run):
check_toolchain('nvcc', 'gcc-compatible', 'g++-compatible')
self.assertEqual(len(calls), 3)
self.assertEqual(calls[1][2], 'gcc-compatible')
self.assertIn('-c', calls[1]) # A C driver need not link the C++ runtime.
self.assertEqual(calls[2][2], 'g++-compatible')
self.assertNotIn('-c', calls[2])
def test_mpich_architectures_follow_toolkit_and_user_selection(self):
self.assertEqual(mpich_cuda_architectures('86-real;90-virtual;86', 'nvcc'), '86,90')
self.assertEqual(mpich_cuda_architectures('native', 'nvcc'), 'auto')
with patch('build_mfem_bundle.output', return_value='sm_75\nsm_80\nsm_86\nsm_90'):
self.assertEqual(mpich_cuda_architectures('all-major', 'nvcc'), '75,80,90')
self.assertEqual(mpich_cuda_architectures('all', 'nvcc'), '75,80,86,90')
with self.assertRaisesRegex(RuntimeError, 'Invalid CUDA architecture'):
mpich_cuda_architectures('garbage', 'nvcc')
def test_hypre_patch_preserves_archive_and_older_cuda_branch(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
original = root / 'original'
utilities = original / 'src' / 'utilities'
utilities.mkdir(parents=True)
header = ('#include <thrust/execution_policy.h>\n'
'#define HYPRE_THRUST_NOT(pred) thrust::not1(pred)\n'
'using namespace thrust::placeholders;\n')
for name in ('device_utils.h', '_hypre_utilities.hpp'):
(utilities / name).write_text(header)
memory = ''.join(
f'HYPRE_CUDA_CALL( cudaMemPrefetchAsync(ptr, size, {device},\n'
' hypre_HandleComputeStream(hypre_handle())) );\n'
for device in ('hypre_HandleDevice(hypre_handle())', 'cudaCpuDeviceId'))
(utilities / 'memory.c').write_text(memory)
args = SimpleNamespace(hypre_source=original, work_dir=root / 'work')
self.assertEqual(prepare_hypre_source(args, {'cuda': False}), original)
patched = prepare_hypre_source(args, {'cuda': True})
self.assertEqual((utilities / 'memory.c').read_text(), memory)
updated = (patched / 'src/utilities/memory.c').read_text()
self.assertIn('#if CUDART_VERSION >= 13000', updated)
self.assertIn('cudaMemLocationTypeHost, 0', updated)
self.assertIn('cudaMemPrefetchAsync(ptr, size, cudaCpuDeviceId,', updated)
self.assertIn('#define HYPRE_THRUST_NOT(pred) thrust::not1(pred)',
(patched / 'src/utilities/device_utils.h').read_text())
prepare_hypre_source(args, {'cuda': True})
self.assertEqual((patched / 'src/utilities/memory.c').read_text(), updated)
def test_cxx_failure_reports_toolkit_host_and_recovery(self):
results = [subprocess.CompletedProcess([], 0, 'CUDA test toolkit', ''),
subprocess.CompletedProcess([], 0, '', ''),
subprocess.CompletedProcess([], 1, '', 'unsupported Microsoft Visual Studio version')]
with patch('subprocess.run', side_effect=results):
with self.assertRaises(RuntimeError) as caught:
check_toolchain('nvcc.exe', 'cl.exe', 'cl.exe')
message = str(caught.exception)
for expected in ['C++ compiler: cl.exe', 'CUDA test toolkit',
'unsupported Microsoft Visual Studio version', 'fresh Meson build directory']:
self.assertIn(expected, message)
if __name__ == '__main__':
unittest.main()