forked from kaihsin/hptt
-
Notifications
You must be signed in to change notification settings - Fork 0
Add CI: build matrix for Linux/macOS/Windows on x64 and ARM64 #6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 2 commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
5c2cf81
Add a downstream consumer for find_package(hptt CONFIG)
IvanaGyro 51dcba0
Add a build workflow for Linux, macOS and Windows on x64 and ARM64
IvanaGyro 59a2b8c
Apply the baseline -march=x86-64 flag only on x86 targets
IvanaGyro 9828f5d
Pass OpenMP_ROOT when configuring the downstream consumer on macOS
IvanaGyro 12b29fd
Apply -mfpu=neon only on 32-bit ARM targets
IvanaGyro 4575e1d
Build the ARM jobs with ENABLE_ARM=ON
IvanaGyro 2931cd8
Key the -mfpu=neon guard on the compiler that has the flag
IvanaGyro File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,226 @@ | ||
| name: Build | ||
|
|
||
| # Builds HPTT on Linux, macOS and Windows, on both x86-64 and ARM64, and | ||
| # then consumes it from a separate project through | ||
| # `find_package(hptt CONFIG)` -- once against the build tree and once | ||
| # against an install prefix. The consumer transposes random tensors and | ||
| # compares against a naive reference, so a green job means the exported | ||
| # package links *and* the library computes correct results on that | ||
| # platform. | ||
|
|
||
| on: | ||
| # No `branches:` filter: a pull request targeting any branch is built, | ||
| # which is what makes stacked PRs (one PR based on another's branch) | ||
| # get checked too. | ||
| pull_request: | ||
| push: | ||
| branches: | ||
| - main | ||
| workflow_dispatch: | ||
|
|
||
| concurrency: | ||
| # One group per PR, so pushing a new commit cancels the runs still | ||
| # queued or in flight for the previous one. Pushes to main are keyed by | ||
| # run_id instead and never cancel each other -- every merged commit | ||
| # keeps its own result. | ||
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} | ||
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | ||
|
|
||
| jobs: | ||
| build: | ||
| name: ${{ matrix.name }} | ||
| runs-on: ${{ matrix.runner }} | ||
| strategy: | ||
| # One platform failing should not hide the results of the others. | ||
| fail-fast: false | ||
| matrix: | ||
| include: | ||
| - name: linux-x64 | ||
| runner: ubuntu-latest | ||
| - name: linux-arm64 | ||
| runner: ubuntu-24.04-arm | ||
| - name: macos-x64 | ||
| runner: macos-15-intel | ||
| - name: macos-arm64 | ||
| runner: macos-latest | ||
| - name: windows-x64 | ||
| runner: windows-latest | ||
| msvc_arch: x64 | ||
| - name: windows-arm64 | ||
| runner: windows-11-arm | ||
| msvc_arch: arm64 | ||
|
|
||
| defaults: | ||
| run: | ||
| # bash everywhere, including Windows, so one set of steps covers | ||
| # all six jobs. -e / -o pipefail restore the fail-fast behaviour | ||
| # that overriding `shell:` would otherwise drop. | ||
| shell: bash -eo pipefail {0} | ||
|
|
||
| env: | ||
| BUILD_DIR: ${{ github.workspace }}/build | ||
| INSTALL_DIR: ${{ github.workspace }}/install | ||
| CONSUMER_SRC_DIR: ${{ github.workspace }}/tests/downstream_find_package | ||
|
|
||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| with: | ||
| fetch-depth: 0 | ||
|
|
||
| # Re-running a PR job reuses the merge commit built when the run was | ||
| # first created, not a fresh merge with the current target branch. | ||
| # Merging here keeps the job honest about the branch as it stands now. | ||
| # A committer identity is required because a non-fast-forward merge | ||
| # creates a merge commit. | ||
| - name: Merge with latest target branch (pull_request only) | ||
| if: github.event_name == 'pull_request' | ||
| run: | | ||
| git config user.name "github-actions[bot]" | ||
| git config user.email "github-actions[bot]@users.noreply.github.com" | ||
| git fetch origin ${{ github.event.pull_request.base.ref }} | ||
| git merge --no-edit origin/${{ github.event.pull_request.base.ref }} | ||
|
|
||
| # AppleClang ships no OpenMP runtime, so find_package(OpenMP REQUIRED) | ||
| # fails on macOS without libomp. OpenMP_ROOT is passed at configure | ||
| # time below. | ||
| - name: Install libomp (macOS) | ||
| if: runner.os == 'macOS' | ||
| run: brew install libomp | ||
|
|
||
| # The runner images do not all ship Ninja, and the ones that do are | ||
| # not consistent about it across architectures. | ||
| - name: Install Ninja | ||
| run: | | ||
| if command -v ninja >/dev/null 2>&1; then | ||
| ninja --version | ||
| exit 0 | ||
| fi | ||
| case "$RUNNER_OS" in | ||
| Linux) sudo apt-get update && sudo apt-get install -y ninja-build ;; | ||
| macOS) brew install ninja ;; | ||
| Windows) choco install ninja -y --no-progress ;; | ||
| esac | ||
| ninja --version | ||
|
|
||
| # Puts cl.exe, the Windows SDK headers and the import libraries on | ||
| # PATH/INCLUDE/LIB. The Ninja generator, unlike the Visual Studio | ||
| # one, does not locate the toolchain by itself. Ninja itself is | ||
| # preinstalled on the Windows runner images. | ||
| - name: Set up MSVC (Windows) | ||
| if: runner.os == 'Windows' | ||
| uses: ilammy/msvc-dev-cmd@v1 | ||
| with: | ||
| arch: ${{ matrix.msvc_arch }} | ||
|
|
||
| # Installs ccache for the current OS and restores/saves its cache. | ||
| # The key is per matrix job so the six platforms never share objects. | ||
| - name: ccache | ||
| uses: hendrikmuhs/ccache-action@v1.2 | ||
| with: | ||
| key: ${{ matrix.name }} | ||
| max-size: 500M | ||
|
|
||
| - name: Configure HPTT | ||
| # ccache is wired in through CMake's compiler-launcher hook rather | ||
| # than by shadowing the compiler on PATH, which keeps working with | ||
| # the absolute compiler paths CMake records. | ||
| # | ||
| # CMP0141 + MSVC_DEBUG_INFORMATION_FORMAT=Embedded put debug info | ||
| # in the object files (/Z7). ccache cannot cache compilations that | ||
| # write to a shared .pdb, so without this every Windows compile is | ||
| # a cache miss. | ||
| run: | | ||
| EXTRA=() | ||
| if [ "$RUNNER_OS" = "macOS" ]; then | ||
| EXTRA+=("-DOpenMP_ROOT=$(brew --prefix libomp)") | ||
| fi | ||
| if [ "$RUNNER_OS" = "Windows" ]; then | ||
| EXTRA+=(-DCMAKE_C_COMPILER=cl -DCMAKE_CXX_COMPILER=cl) | ||
| EXTRA+=(-DCMAKE_POLICY_DEFAULT_CMP0141=NEW) | ||
| EXTRA+=(-DCMAKE_MSVC_DEBUG_INFORMATION_FORMAT=Embedded) | ||
| fi | ||
| cmake -S . -B "$BUILD_DIR" -G Ninja \ | ||
| -DCMAKE_BUILD_TYPE=Release \ | ||
| -DCMAKE_INSTALL_PREFIX="$INSTALL_DIR" \ | ||
| -DCMAKE_C_COMPILER_LAUNCHER=ccache \ | ||
| -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ | ||
| "${EXTRA[@]}" | ||
|
|
||
| - name: Build HPTT | ||
| run: cmake --build "$BUILD_DIR" | ||
|
|
||
| # --- Downstream consumer: build tree ------------------------------ | ||
| # export(TARGETS ...) plus the build-dir hpttConfig.cmake make the | ||
| # package resolvable before anything is installed; hptt_DIR points | ||
| # find_package straight at the build directory. | ||
| - name: Consumer against the BUILD TREE | ||
| run: | | ||
| EXTRA=() | ||
| [ "$RUNNER_OS" = "Windows" ] && EXTRA+=(-DCMAKE_CXX_COMPILER=cl) | ||
| cmake -S "$CONSUMER_SRC_DIR" -B "${{ github.workspace }}/consumer-build-tree" \ | ||
| -G Ninja -DCMAKE_BUILD_TYPE=Release \ | ||
| -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ | ||
| -Dhptt_DIR="$BUILD_DIR" "${EXTRA[@]}" | ||
|
IvanaGyro marked this conversation as resolved.
|
||
| cmake --build "${{ github.workspace }}/consumer-build-tree" | ||
|
|
||
| - name: Run BUILD TREE consumer | ||
| run: | | ||
| cd "${{ github.workspace }}/consumer-build-tree" | ||
| ./consumer | ||
| ./consumer_dyn | ||
|
|
||
| # --- Downstream consumer: install tree ---------------------------- | ||
| - name: Install HPTT | ||
| run: cmake --install "$BUILD_DIR" | ||
|
|
||
| - name: Consumer against the INSTALL TREE | ||
| # A fresh build directory resolving the package from the install | ||
| # prefix through CMAKE_PREFIX_PATH, the way a real downstream user | ||
| # would find an installed HPTT. | ||
| run: | | ||
| EXTRA=() | ||
| [ "$RUNNER_OS" = "Windows" ] && EXTRA+=(-DCMAKE_CXX_COMPILER=cl) | ||
| cmake -S "$CONSUMER_SRC_DIR" -B "${{ github.workspace }}/consumer-install-tree" \ | ||
| -G Ninja -DCMAKE_BUILD_TYPE=Release \ | ||
| -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ | ||
| -DCMAKE_PREFIX_PATH="$INSTALL_DIR" "${EXTRA[@]}" | ||
| cmake --build "${{ github.workspace }}/consumer-install-tree" | ||
|
|
||
| - name: Run INSTALL TREE consumer | ||
| run: | | ||
| cd "${{ github.workspace }}/consumer-install-tree" | ||
| ./consumer | ||
| ./consumer_dyn | ||
|
|
||
| # Guards the artifact-naming rule the Windows build depends on: the | ||
| # static library and the DLL's import library must never claim the | ||
| # same path. The Ninja generator only rejects the collision when both | ||
| # targets are enabled, which is the configuration built above. | ||
| - name: Check Windows artifact names are distinct | ||
| if: runner.os == 'Windows' | ||
| run: | | ||
| test -f "$INSTALL_DIR/lib/hptt.lib" | ||
| test -f "$INSTALL_DIR/lib/hptt_dyn.lib" | ||
| test -f "$INSTALL_DIR/bin/hptt.dll" | ||
|
|
||
| # HPTT_BUILD_SHARED=OFF has to keep configuring, building and | ||
| # exporting on its own -- it is how a parent project that links only | ||
| # the static archive embeds HPTT. | ||
| - name: Build with HPTT_BUILD_SHARED=OFF | ||
| run: | | ||
| EXTRA=() | ||
| if [ "$RUNNER_OS" = "macOS" ]; then | ||
| EXTRA+=("-DOpenMP_ROOT=$(brew --prefix libomp)") | ||
| fi | ||
| if [ "$RUNNER_OS" = "Windows" ]; then | ||
| EXTRA+=(-DCMAKE_C_COMPILER=cl -DCMAKE_CXX_COMPILER=cl) | ||
| EXTRA+=(-DCMAKE_POLICY_DEFAULT_CMP0141=NEW) | ||
| EXTRA+=(-DCMAKE_MSVC_DEBUG_INFORMATION_FORMAT=Embedded) | ||
| fi | ||
| cmake -S . -B "${{ github.workspace }}/build-static-only" -G Ninja \ | ||
| -DCMAKE_BUILD_TYPE=Release \ | ||
| -DHPTT_BUILD_SHARED=OFF \ | ||
| -DCMAKE_C_COMPILER_LAUNCHER=ccache \ | ||
| -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ | ||
| "${EXTRA[@]}" | ||
| cmake --build "${{ github.workspace }}/build-static-only" | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| # Standalone consumer used by .github/workflows/ci-build.yml. | ||
| # | ||
| # It is deliberately *not* part of the top-level HPTT build: it resolves | ||
| # HPTT the way a real downstream project does, through | ||
| # `find_package(hptt CONFIG)` and the `hptt::` imported targets, so it | ||
| # exercises the generated hpttConfig.cmake / hpttTargets.cmake rather than | ||
| # the in-tree target names. The workflow configures it twice, once against | ||
| # the build tree (via hptt_DIR) and once against an install prefix (via | ||
| # CMAKE_PREFIX_PATH). | ||
| cmake_minimum_required(VERSION 3.21) | ||
| project(hptt_downstream_consumer LANGUAGES CXX) | ||
|
|
||
| find_package(hptt CONFIG REQUIRED) | ||
|
|
||
| add_executable(consumer consumer.cpp) | ||
| target_link_libraries(consumer PRIVATE hptt::hptt_static) | ||
|
|
||
| # hptt::hptt_dyn only exists when HPTT was built with HPTT_BUILD_SHARED=ON, | ||
| # so a static-only HPTT still configures here. | ||
| if(TARGET hptt::hptt_dyn) | ||
| add_executable(consumer_dyn consumer.cpp) | ||
| target_link_libraries(consumer_dyn PRIVATE hptt::hptt_dyn) | ||
|
|
||
| # On Windows the DLL has to sit next to the executable to be found at | ||
| # run time. TARGET_RUNTIME_DLLS is empty on platforms that use rpath, | ||
| # so the command is skipped there rather than guarded on WIN32. | ||
| add_custom_command(TARGET consumer_dyn POST_BUILD | ||
| COMMAND ${CMAKE_COMMAND} -E $<IF:$<BOOL:$<TARGET_RUNTIME_DLLS:consumer_dyn>>,copy,true> | ||
| $<TARGET_RUNTIME_DLLS:consumer_dyn> $<TARGET_FILE_DIR:consumer_dyn> | ||
| COMMAND_EXPAND_LISTS) | ||
| endif() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,121 @@ | ||
| // Correctness check for HPTT: transposes random tensors and compares against | ||
| // a naive index-arithmetic reference. Covers all four instantiated scalar | ||
| // types, several dimensionalities/permutations, and beta != 0. | ||
| #include <hptt.h> | ||
|
|
||
| #include <algorithm> | ||
| #include <complex> | ||
| #include <cstdio> | ||
| #include <random> | ||
| #include <vector> | ||
|
|
||
| namespace { | ||
|
|
||
| int failures = 0; | ||
|
|
||
| inline double mag(float v) { return std::abs(static_cast<double>(v)); } | ||
| inline double mag(double v) { return std::abs(v); } | ||
| inline double mag(std::complex<float> v) { return std::abs(std::complex<double>(v)); } | ||
| inline double mag(std::complex<double> v) { return std::abs(v); } | ||
|
|
||
| template <typename T> struct Sampler { | ||
| static T get(std::mt19937 &rng) { | ||
| std::uniform_real_distribution<double> d(-1.0, 1.0); | ||
| return static_cast<T>(d(rng)); | ||
| } | ||
| }; | ||
| template <typename R> struct Sampler<std::complex<R>> { | ||
| static std::complex<R> get(std::mt19937 &rng) { | ||
| std::uniform_real_distribution<double> d(-1.0, 1.0); | ||
| return std::complex<R>(static_cast<R>(d(rng)), static_cast<R>(d(rng))); | ||
| } | ||
| }; | ||
|
|
||
| // B_{perm(i)} = alpha * A_i + beta * B_{perm(i)}, column-major on both sides. | ||
| template <typename T> | ||
| void reference(const std::vector<int> &sizeA, const std::vector<int> &perm, T alpha, | ||
| const std::vector<T> &A, T beta, std::vector<T> &B) { | ||
| const int dim = static_cast<int>(sizeA.size()); | ||
| std::vector<int> sizeB(dim); | ||
| for (int k = 0; k < dim; ++k) sizeB[k] = sizeA[perm[k]]; | ||
|
|
||
| std::vector<size_t> ldA(dim, 1), ldB(dim, 1); | ||
| for (int k = 1; k < dim; ++k) ldA[k] = ldA[k - 1] * static_cast<size_t>(sizeA[k - 1]); | ||
| for (int k = 1; k < dim; ++k) ldB[k] = ldB[k - 1] * static_cast<size_t>(sizeB[k - 1]); | ||
|
|
||
| std::vector<int> idx(dim, 0); | ||
| for (size_t n = 0; n < A.size(); ++n) { | ||
| size_t offA = 0, offB = 0; | ||
| for (int k = 0; k < dim; ++k) offA += static_cast<size_t>(idx[k]) * ldA[k]; | ||
| for (int k = 0; k < dim; ++k) offB += static_cast<size_t>(idx[perm[k]]) * ldB[k]; | ||
| B[offB] = alpha * A[offA] + beta * B[offB]; | ||
| for (int k = 0; k < dim; ++k) { | ||
| if (++idx[k] < sizeA[k]) break; | ||
| idx[k] = 0; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| template <typename T> | ||
| void run(const char *label, const std::vector<int> &sizeA, const std::vector<int> &perm, | ||
| T alpha, T beta, int numThreads, double limit) { | ||
| const int dim = static_cast<int>(sizeA.size()); | ||
| size_t total = 1; | ||
| for (int s : sizeA) total *= static_cast<size_t>(s); | ||
|
|
||
| std::mt19937 rng(12345u + static_cast<unsigned>(dim) * 7u); | ||
| std::vector<T> A(total), B(total); | ||
| for (size_t i = 0; i < total; ++i) A[i] = Sampler<T>::get(rng); | ||
| for (size_t i = 0; i < total; ++i) B[i] = Sampler<T>::get(rng); | ||
| std::vector<T> Bref = B; | ||
|
|
||
| reference<T>(sizeA, perm, alpha, A, beta, Bref); | ||
|
|
||
| auto plan = hptt::create_plan(perm.data(), dim, alpha, A.data(), sizeA.data(), nullptr, | ||
| beta, B.data(), nullptr, hptt::ESTIMATE, numThreads); | ||
| plan->execute(); | ||
|
|
||
| double worst = 0.0; | ||
| for (size_t i = 0; i < total; ++i) worst = std::max(worst, mag(B[i] - Bref[i])); | ||
|
|
||
| if (!(worst <= limit)) { | ||
| std::printf(" FAIL %-34s max|B-Bref| = %g\n", label, worst); | ||
| ++failures; | ||
| } else { | ||
| std::printf(" ok %-34s max|B-Bref| = %g\n", label, worst); | ||
| } | ||
| } | ||
|
|
||
| } // namespace | ||
|
|
||
| int main() { | ||
| const double ftol = 1e-4, dtol = 1e-12; | ||
|
|
||
| std::printf("hptt correctness (numThreads=1):\n"); | ||
| run<float>("float 2D {64,32} perm{1,0}", {64, 32}, {1, 0}, 1.0f, 0.0f, 1, ftol); | ||
| run<double>("double 2D {64,32} perm{1,0}", {64, 32}, {1, 0}, 1.0, 0.0, 1, dtol); | ||
| run<double>("double 3D {17,9,13} perm{2,0,1}", {17, 9, 13}, {2, 0, 1}, 1.0, 0.0, 1, dtol); | ||
| run<double>("double 3D beta!=0", {17, 9, 13}, {2, 0, 1}, 2.5, -1.25, 1, dtol); | ||
| run<double>("double 4D {8,7,6,5} perm{3,1,0,2}", {8, 7, 6, 5}, {3, 1, 0, 2}, 1.0, 0.0, 1, dtol); | ||
| run<double>("double 5D {5,4,3,6,7} perm{4,0,3,1,2}", {5, 4, 3, 6, 7}, {4, 0, 3, 1, 2}, 1.0, 0.0, 1, dtol); | ||
| run<double>("double identity perm {33,21}", {33, 21}, {0, 1}, 1.0, 0.0, 1, dtol); | ||
| run<std::complex<float>>("cfloat 3D perm{2,1,0}", {12, 10, 9}, {2, 1, 0}, | ||
| std::complex<float>(1.0f, 0.0f), std::complex<float>(0.0f, 0.0f), 1, ftol); | ||
| run<std::complex<double>>("cdouble 3D alpha,beta complex", {12, 10, 9}, {2, 1, 0}, | ||
| std::complex<double>(1.5, -0.5), std::complex<double>(0.25, 0.75), 1, dtol); | ||
|
|
||
| std::printf("hptt correctness (numThreads=4):\n"); | ||
| run<double>("double 2D large {512,256}", {512, 256}, {1, 0}, 1.0, 0.0, 4, dtol); | ||
| run<double>("double 3D large {128,64,32}", {128, 64, 32}, {2, 0, 1}, 1.0, 0.0, 4, dtol); | ||
| run<double>("double 4D large beta!=0", {32, 24, 16, 12}, {3, 1, 0, 2}, 1.5, 0.5, 4, dtol); | ||
| run<float>("float 3D large {128,64,32}", {128, 64, 32}, {1, 2, 0}, 1.0f, 0.0f, 4, ftol); | ||
| run<std::complex<double>>("cdouble 3D large", {64, 32, 16}, {2, 0, 1}, | ||
| std::complex<double>(1.0, 0.0), std::complex<double>(0.0, 0.0), 4, dtol); | ||
|
|
||
| if (failures) { | ||
| std::printf("\n%d FAILURE(S)\n", failures); | ||
| return 1; | ||
| } | ||
| std::printf("\nall checks passed\n"); | ||
| return 0; | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.