diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 155f7872..d4033698 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,6 +42,7 @@ jobs: pull-requests: read outputs: lanes: ${{ steps.diff.outputs.lanes }} + device: ${{ steps.diff.outputs.device }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -77,6 +78,12 @@ jobs: done fi echo "lanes=$lanes" | tee -a "$GITHUB_OUTPUT" + # Whether a device build could have changed; without a diff to read, it could. + device=true + if [ -f changed.txt ] && ! grep -vE '\.md$' changed.txt | grep -qE '^native/|^\.github/workflows/ci\.yml$'; then + device=false + fi + echo "device=$device" | tee -a "$GITHUB_OUTPUT" docs-tests: needs: [changes, native-wheel] @@ -567,6 +574,103 @@ jobs: - run: uv sync --locked --no-install-package solprop-native - run: uv run poe check-native + # One compile of each device target on a change to native/, on a hosted runner with no + # device, tests included: what breaks a user's first GPU build is found here and not + # when someone next rents a card. Nothing here runs on a device, which stays by hand + # (#354), and a fault only a card shows still needs one. + device-compile-cuda: + needs: changes + if: needs.changes.outputs.lanes == 'full' && needs.changes.outputs.device == 'true' + name: device-compile (CUDA 12.8) + runs-on: ubuntu-24.04 + timeout-minutes: 150 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + # The full history: the native version comes from the tags. + fetch-depth: 0 + - uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e # v10.3.2 + with: + enable-cache: true + - name: Make room for the toolkit + run: | + sudo rm -rf /usr/local/lib/android /usr/share/dotnet /opt/ghc /usr/local/.ghcup + df -h / + - name: Install CUDA 12.8, the floor + run: | + curl -fsSL https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2404/x86_64/cuda-keyring_1.1-1_all.deb -o keyring.deb + sudo dpkg -i keyring.deb + sudo apt-get update + sudo apt-get install --yes --no-install-recommends cmake g++-14 libfftw3-dev libopenblas-dev ninja-build pkg-config cuda-compiler-12-8 cuda-libraries-dev-12-8 + df -h / + - run: uv python install 3.14 + - run: uv sync --locked --no-install-package solprop-native + - name: Configure + working-directory: native + env: + NVCC_WRAPPER_DEFAULT_COMPILER: g++-14 + run: | + export PATH="/usr/local/cuda-12.8/bin:$PATH" + cmake --preset cuda \ + -DCMAKE_C_COMPILER=gcc-14 -DCMAKE_CXX_COMPILER=g++-14 -DCMAKE_CUDA_HOST_COMPILER=g++-14 \ + -DCUDA_ROOT=/usr/local/cuda-12.8 \ + -DSOLPROP_NATIVE_BUILD_BENCHMARKS=OFF \ + -DPython_EXECUTABLE="$GITHUB_WORKSPACE/.venv/bin/python" + - name: Compile, tests included + working-directory: native + env: + NVCC_WRAPPER_DEFAULT_COMPILER: g++-14 + run: | + export PATH="/usr/local/cuda-12.8/bin:$PATH" + cmake --build --preset cuda --parallel 4 + df -h / + + device-compile-hip: + needs: changes + if: needs.changes.outputs.lanes == 'full' && needs.changes.outputs.device == 'true' + name: device-compile (HIP, ROCm 7.2) + runs-on: ubuntu-24.04 + timeout-minutes: 150 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + - uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e # v10.3.2 + with: + enable-cache: true + - name: Make room for the toolkit + run: | + sudo rm -rf /usr/local/lib/android /usr/share/dotnet /opt/ghc /usr/local/.ghcup + df -h / + - name: Install ROCm + run: | + curl -fsSL https://repo.radeon.com/rocm/rocm.gpg.key | gpg --dearmor | sudo tee /etc/apt/keyrings/rocm.gpg > /dev/null + echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/rocm.gpg] https://repo.radeon.com/rocm/apt/7.2 noble main" | sudo tee /etc/apt/sources.list.d/rocm.list + # Ubuntu has an older hipcc whose version number sorts higher than AMD's. + printf 'Package: *\nPin: origin repo.radeon.com\nPin-Priority: 1001\n' | sudo tee /etc/apt/preferences.d/rocm + sudo apt-get update + sudo apt-get install --yes --no-install-recommends cmake libfftw3-dev libopenblas-dev ninja-build pkg-config g++-14 hip-dev hipcc rocm-llvm rocm-device-libs rocm-cmake rocfft-dev rocblas-dev + echo "ROCM_PATH=/opt/rocm" >> "$GITHUB_ENV" + /opt/rocm/bin/hipcc --version + df -h / + - run: uv python install 3.14 + - run: uv sync --locked --no-install-package solprop-native + - name: Configure + working-directory: native + run: | + export PATH="$ROCM_PATH/bin:$PATH" + cmake --preset hip \ + -DCMAKE_CXX_COMPILER="$ROCM_PATH/bin/hipcc" -DCMAKE_PREFIX_PATH="$ROCM_PATH" \ + -DCMAKE_CXX_SCAN_FOR_MODULES=OFF -DKokkos_ENABLE_ROCTHRUST=OFF \ + -DSOLPROP_NATIVE_BUILD_BENCHMARKS=OFF \ + -DPython_EXECUTABLE="$GITHUB_WORKSPACE/.venv/bin/python" + - name: Compile, tests included + working-directory: native + run: | + export PATH="$ROCM_PATH/bin:$PATH" + cmake --build --preset hip --parallel 4 + df -h / + # The front end's gate (`poe check-gui`: its types, docstrings and tests, the layers # without Qt held to 100 % coverage) on each desktop platform, with the widgets drawn # offscreen. Its lint, format, spelling and licence checks are the static job's. @@ -617,6 +721,8 @@ jobs: - oldest - no-native - native-quality + - device-compile-cuda + - device-compile-hip - gui runs-on: ubuntu-latest timeout-minutes: 5 diff --git a/changelog.d/151-device-compile.changed.md b/changelog.d/151-device-compile.changed.md new file mode 100644 index 00000000..835b6e50 --- /dev/null +++ b/changelog.d/151-device-compile.changed.md @@ -0,0 +1 @@ +Continuous integration now compiles the CUDA and HIP targets on a change to `native/`, with no device, so a change that breaks a GPU build is found before it merges. A build from source no longer stops on a compiler's warning: `SOLPROP_NATIVE_WARNINGS_AS_ERRORS` now follows `BUILD_TESTING`, so it stays on for the presets and is off for `pip` and `uv` builds, while continuous integration keeps it on for its wheels. (#151) diff --git a/docs/dev/tooling.md b/docs/dev/tooling.md index 75ad0b27..683829aa 100644 --- a/docs/dev/tooling.md +++ b/docs/dev/tooling.md @@ -244,6 +244,10 @@ jaxtyping hook) run in the coverage lane only; the other test lanes set its lint and strict types, clang-format and its pytest suite (`lint pytest`); clang-tidy and the native tests (`tidy`); and ASan and UBSan (`sanitize`). The distribution wheel's stage is native-wheel's Linux job. +- **device-compile** (Linux, two jobs): on a change to `native/`, compiles the `cuda` + preset under CUDA 12.8 and the `hip` preset under ROCm 7.2, tests included, with warnings + as errors and no device. About 25 and 11 minutes, with no cache. A fault only a card + shows, or a test that fails on one, still needs a run on a device. - **gui** (Linux x86-64, macOS arm64, Windows x64): `uv run poe check-gui`, the front end's gate (its types against `gui/pyproject.toml`, its docstrings and its tests, with the layers without Qt held to 100 % coverage), after `uv sync --extra gui --group diff --git a/docs/guide/performance/building-native.md b/docs/guide/performance/building-native.md index 5da0482e..b2bff969 100644 --- a/docs/guide/performance/building-native.md +++ b/docs/guide/performance/building-native.md @@ -388,7 +388,7 @@ The options are read by `native/CMakeLists.txt` and `native/cmake/HostLibraries. | option | default | values and effect | |---|---|---| | `BUILD_TESTING` | `OFF` in pip and uv builds, `ON` in the presets | builds the GoogleTest native tests and registers them with CTest | -| `SOLPROP_NATIVE_WARNINGS_AS_ERRORS` | `ON` | `-Werror` on the package's own sources | +| `SOLPROP_NATIVE_WARNINGS_AS_ERRORS` | as `BUILD_TESTING`: `OFF` in pip and uv builds, `ON` in the presets | `-Werror` on the package's own sources. Continuous integration turns it on for its wheels too | | `SOLPROP_NATIVE_ENABLE_CLANG_TIDY` | `OFF` | runs clang-tidy while compiling the package's sources | | `SOLPROP_NATIVE_ENABLE_SANITIZERS` | `OFF` | AddressSanitizer and UBSan on the package's targets; CPU builds only | | `SOLPROP_NATIVE_BUILD_BENCHMARKS` | `OFF` | builds the `solprop_native_gemm_benchmark` microbenchmark (never installed) | diff --git a/native/AGENTS.md b/native/AGENTS.md index 63a2743b..4480e6a5 100644 --- a/native/AGENTS.md +++ b/native/AGENTS.md @@ -88,7 +88,8 @@ the `native-quality` jobs run `native/scripts/check.sh` on Linux, one group of i each (`SOLPROP_NATIVE_CHECK_STAGES`). The `native-wheel` jobs build the CPU distribution wheel on Linux, macOS and Windows, cached under a hash of `native/`, and solprop's default lane runs on it on macOS and Windows with the native backend selected, and in -the coverage lane on Linux. GPU builds are validated by hand +the coverage lane on Linux. The `device-compile` lanes compile the CUDA and HIP targets, tests included, on a change to +`native/`, with no device. Running on one stays by hand (`.github/workflows/cuda.yml` on a self-hosted runner, or `pytest -m gpu` on a machine with a device). diff --git a/native/CMakeLists.txt b/native/CMakeLists.txt index 6e5aeca0..22a1d9ca 100644 --- a/native/CMakeLists.txt +++ b/native/CMakeLists.txt @@ -281,8 +281,10 @@ option(SOLPROP_NATIVE_ENABLE_AGGRESSIVE_VECTORIZATION "Assert independent Kokkos loop iterations to the vectorizer" ON) option(SOLPROP_NATIVE_ENABLE_IPO "Enable checked interprocedural/link-time optimization for CPU builds" ON) +# A build with its tests is development, and CI, and stops on a warning. A user's build +# from source isn't: one warning from a newer compiler shouldn't stop it. option(SOLPROP_NATIVE_WARNINGS_AS_ERRORS - "Treat diagnostics in project-owned C++ sources as errors" ON) + "Treat diagnostics in project-owned C++ sources as errors" ${BUILD_TESTING}) option(SOLPROP_NATIVE_ENABLE_CLANG_TIDY "Run clang-tidy while compiling project-owned C++ sources" OFF) option(SOLPROP_NATIVE_ENABLE_SANITIZERS diff --git a/native/pyproject.toml b/native/pyproject.toml index 18006419..971d903f 100644 --- a/native/pyproject.toml +++ b/native/pyproject.toml @@ -84,7 +84,7 @@ build = "cp314-*" skip = ["cp314t-*", "*-musllinux_*"] archs = ["auto64"] build-verbosity = 1 -config-settings = { "cmake.define.SOLPROP_NATIVE_PROFILE" = "DISTRIBUTION", "build-dir" = "build/cibw-{wheel_tag}" } +config-settings = { "cmake.define.SOLPROP_NATIVE_PROFILE" = "DISTRIBUTION", "cmake.define.SOLPROP_NATIVE_WARNINGS_AS_ERRORS" = "ON", "build-dir" = "build/cibw-{wheel_tag}" } # A first smoke test in cibuildwheel's test environment, beside solprop from the same # checkout (check_build reads solprop's kernel contract); the suite and the # coexistence check run in the workflow's test jobs. diff --git a/native/scripts/check.sh b/native/scripts/check.sh index 0d206031..ed30cd27 100755 --- a/native/scripts/check.sh +++ b/native/scripts/check.sh @@ -168,7 +168,7 @@ if ! stage wheel; then fi quality_temp=$(mktemp -d "${TMPDIR:-/tmp}/solprop-native-check.XXXXXX") trap 'rm -rf "$quality_temp"' EXIT -distribution_wheel=(-Ccmake.define.SOLPROP_NATIVE_PROFILE=DISTRIBUTION "-Cbuild-dir=build/distribution-{wheel_tag}") +distribution_wheel=(-Ccmake.define.SOLPROP_NATIVE_PROFILE=DISTRIBUTION -Ccmake.define.SOLPROP_NATIVE_WARNINGS_AS_ERRORS=ON "-Cbuild-dir=build/distribution-{wheel_tag}") if [[ -n "$qualification_python" ]]; then uv build --wheel --no-build-isolation "${distribution_wheel[@]}" --out-dir "$quality_temp/wheel" else diff --git a/native/tests/native/test_aperture.cpp b/native/tests/native/test_aperture.cpp index b65c7be3..a518b184 100644 --- a/native/tests/native/test_aperture.cpp +++ b/native/tests/native/test_aperture.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -521,11 +522,13 @@ TEST(FarFieldPass, TheShapesAreTheirFormulas) { // The offset is X - x, so x >= X reads across <= 0, whichever side is asked for. EXPECT_EQ(native::far_field_offset(2.0, -3.0, 1.0), -5.0); const auto plane = outline(0.0, 0.0, 1.0, 1.0, 0.0); - for (const auto& [across, kept] : {std::pair{-1.0, 1.0}, - {-0.0, 1.0}, - {0.0, 1.0}, - {std::nextafter(0.0, 1.0), 0.0}, - {1.0, 0.0}}) { + // Named in full: nvcc can't deduce a list whose later members are bare braces. + for (const auto& [across, kept] : + std::initializer_list>{{-1.0, 1.0}, + {-0.0, 1.0}, + {0.0, 1.0}, + {std::nextafter(0.0, 1.0), 0.0}, + {1.0, 0.0}}) { SCOPED_TRACE(across); EXPECT_EQ(native::far_field_shaped_weight(across, 9.0, plane, native::far_field_half_plane_x_ge, 0),