From 61476b5f71082a2291ceb5863f2fbcd53edf183d Mon Sep 17 00:00:00 2001 From: xashr Date: Sat, 22 Aug 2026 18:03:29 +0000 Subject: [PATCH] Fix CUDA arch selection: provide a portable default arch list Without an explicit CMAKE_CUDA_ARCHITECTURES, enable_language(CUDA) (CMP0104 NEW) seeds it from nvcc's default arch (sm_75 on CUDA 13, sm_52 on CUDA 12) - it does not query the local GPU - so every build without an explicit list was single-arch, e.g. sm_75 even on an RTX 5090. Default to the llama.cpp/ggml-cuda arch list before that call. The Dockerfile gets a CUDA_DOCKER_ARCH build-arg passthrough for custom arch sets instead of defining its own list. Docs and build_linux.sh are updated to match: portable default, with native or an explicit arch list for fast local builds. --- .devops/cuda.Dockerfile | 11 +++++- CMakeLists.txt | 82 +++++++++++++++++++++++++++++++++-------- README.md | 10 +++++ docs/build/linux.md | 28 +++++++------- docs/docker.md | 6 +++ scripts/build_linux.sh | 2 +- 6 files changed, 107 insertions(+), 32 deletions(-) diff --git a/.devops/cuda.Dockerfile b/.devops/cuda.Dockerfile index a81186ef..80210f68 100644 --- a/.devops/cuda.Dockerfile +++ b/.devops/cuda.Dockerfile @@ -17,6 +17,10 @@ ARG BASE_CUDA_RUN_CONTAINER=docker.io/nvidia/cuda:${CUDA_VERSION}-runtime-ubuntu FROM ${BASE_CUDA_DEV_CONTAINER} AS build ARG GCC_VERSION=14 +# CUDA architectures to compile for. +# - default = the portable default list from CMakeLists.txt +# - for a custom arch set build with --build-arg CUDA_DOCKER_ARCH="89-real;...". +ARG CUDA_DOCKER_ARCH=default # Install build toolchain RUN apt-get update && \ @@ -30,8 +34,10 @@ ENV CC=gcc-${GCC_VERSION} CXX=g++-${GCC_VERSION} CUDAHOSTCXX=g++-${GCC_VERSION} WORKDIR /app COPY . . -# Configure and build -RUN cmake -S . -B build \ +RUN if [ "${CUDA_DOCKER_ARCH}" != "default" ]; then \ + ADDITIONAL_CMAKE_ARGS="-DCMAKE_CUDA_ARCHITECTURES=${CUDA_DOCKER_ARCH}"; \ + fi && \ + cmake -S . -B build \ -DCMAKE_BUILD_TYPE=Release \ -DAUDIOCPP_MODEL_SET=full \ -DENGINE_ENABLE_CPU_ALL_VARIANTS=ON \ @@ -43,6 +49,7 @@ RUN cmake -S . -B build \ -DENGINE_BUILD_EXAMPLES=OFF \ -DENGINE_BUILD_TESTS=OFF \ -DENGINE_BUILD_WARMBENCH=OFF \ + ${ADDITIONAL_CMAKE_ARGS} \ -DCMAKE_EXE_LINKER_FLAGS=-Wl,--allow-shlib-undefined && \ cmake --build build --parallel $(nproc) \ --target audiocpp_cli \ diff --git a/CMakeLists.txt b/CMakeLists.txt index dbb4bed5..7c557cf6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -166,17 +166,74 @@ if (ENGINE_HIP_STRIX_HALO_OPTIMIZATIONS) endif() if (ENGINE_ENABLE_CUDA AND NOT ENGINE_ENABLE_HIP) - enable_language(CUDA) - # 12.0 is a floor, not a target: any 12.x or 13.x works (the Dockerfile pins - # 12.9.0, and docs/build/windows.md ships a CUDA 13 package). The minimum is - # enforced because an older toolkit configures happily and only fails at the - # first .cu file, which reads as a code bug rather than a toolchain one: nvcc - # < 11.6 cannot parse libstdc++ >= 11.3 headers ("parameter packs not expanded - # with '...'" in ), and distros still ship such pairings (Ubuntu - # 22.04's nvidia-cuda-toolkit is 11.5). Fail here, with a pointer, instead. - # The upper bound is the host compiler the chosen toolkit accepts, not CUDA - # itself (e.g. CUDA 12.9 supports GCC <= 14), so it is left to the toolkit. + # Require at least CUDA 12.0. Older toolkits configure fine but die at the + # first .cu file. Run this before enable_language(CUDA) because the version + # is needed for the default arch list below. find_package(CUDAToolkit 12.0 REQUIRED) + + # Define default CUDA architectures for every build path. + # We stay close to llama.cpp/ggml-cuda here, unless there is a reason + # to deviate. See llama.cpp's ggml-cuda CMakeLists.txt for additional + # comments. + # Must be set before enable_language(CUDA), as that call otherwise seeds + # CMAKE_CUDA_ARCHITECTURES from nvcc's default arch (sm_75 for CUDA 13, + # sm_52 for CUDA 12), silently making every build without an explicit + # list single-arch (the seed ignores the local GPU). + # This can be overridden with -DCMAKE_CUDA_ARCHITECTURES=... or CUDAARCHS; + # set to "native" to build for the local GPU only (needs CMake >= 3.24). + + # "native" fallback for CMake < 3.24 + if ((CMAKE_CUDA_ARCHITECTURES STREQUAL "native" OR "$ENV{CUDAARCHS}" STREQUAL "native") + AND CMAKE_VERSION VERSION_LESS "3.24") + message(STATUS "CMAKE_CUDA_ARCHITECTURES=native requires CMake >= 3.24; " + "falling back to the portable default architecture list") + set(CMAKE_CUDA_ARCHITECTURES "") + endif() + + # Define default archs + if ("${CMAKE_CUDA_ARCHITECTURES}" STREQUAL "" AND "$ENV{CUDAARCHS}" STREQUAL "") + # XX-virtual = PTX (JIT, forward-compatible); XX-real = SASS (native). + if (CUDAToolkit_VERSION VERSION_LESS "13") + # 50/61/70 (Maxwell/Pascal/Volta) unsupported by CUDA 13 toolkits + list(APPEND CMAKE_CUDA_ARCHITECTURES 50-virtual 61-virtual 70-virtual) + endif() + list(APPEND CMAKE_CUDA_ARCHITECTURES 75-virtual 80-virtual 86-real) + if (CUDAToolkit_VERSION VERSION_GREATER_EQUAL "11.8") + list(APPEND CMAKE_CUDA_ARCHITECTURES 89-real 90-virtual) + endif() + if (CUDAToolkit_VERSION VERSION_GREATER_EQUAL "12.8") + # 12Xa, not 12X: Blackwell FP4 tensor cores are arch-specific. + list(APPEND CMAKE_CUDA_ARCHITECTURES 120a-real) + endif() + if (CUDAToolkit_VERSION VERSION_GREATER_EQUAL "12.9") + list(APPEND CMAKE_CUDA_ARCHITECTURES 121a-real) + endif() + endif() + + enable_language(CUDA) + + # Upgrade plain 12X (user value or resolved "native") to 12Xa (Blackwell). + foreach (_ARCHS_VAR IN ITEMS CMAKE_CUDA_ARCHITECTURES CMAKE_CUDA_ARCHITECTURES_NATIVE) + set(_FIXED_ARCHS "") + foreach (_ARCH IN LISTS ${_ARCHS_VAR}) + if (_ARCH MATCHES "^12[0-9](-real|-virtual)?$") + string(REGEX REPLACE "^(12[0-9])((-real|-virtual)?)$" "\\1a\\2" _FIXED_ARCH "${_ARCH}") + message(STATUS "Replacing ${_ARCH} in ${_ARCHS_VAR} with ${_FIXED_ARCH}") + list(APPEND _FIXED_ARCHS "${_FIXED_ARCH}") + else() + list(APPEND _FIXED_ARCHS "${_ARCH}") + endif() + endforeach() + set(${_ARCHS_VAR} "${_FIXED_ARCHS}") + endforeach() + + # Resolve "native" for the log; left as-is when no GPU was detected + # (nvcc then warns and uses its default arch). + if (CMAKE_CUDA_ARCHITECTURES STREQUAL "native" AND CMAKE_CUDA_ARCHITECTURES_NATIVE MATCHES "^[0-9]+(a|f)?(-real|-virtual)?(;[0-9]+(a|f)?(-real|-virtual)?|;)*$") + set(CMAKE_CUDA_ARCHITECTURES "${CMAKE_CUDA_ARCHITECTURES_NATIVE}") + endif() + message(STATUS "Using CMAKE_CUDA_ARCHITECTURES=${CMAKE_CUDA_ARCHITECTURES} CMAKE_CUDA_ARCHITECTURES_NATIVE=${CMAKE_CUDA_ARCHITECTURES_NATIVE}") + if (NOT MSVC) set(CMAKE_CUDA_FLAGS_DEBUG "${AUDIOCPP_DEBUG_OPT_FLAGS}" CACHE STRING "Optimized debug CUDA flags" FORCE) endif() @@ -1472,11 +1529,6 @@ if (ENGINE_ENABLE_CUDA AND NOT ENGINE_ENABLE_HIP) CUDA_STANDARD 17 CUDA_STANDARD_REQUIRED ON ) - if (CMAKE_CUDA_ARCHITECTURES) - set_target_properties(engine_runtime PROPERTIES CUDA_ARCHITECTURES "${CMAKE_CUDA_ARCHITECTURES}") - else() - set_target_properties(engine_runtime PROPERTIES CUDA_ARCHITECTURES native) - endif() target_compile_definitions(engine_core PRIVATE ENGINE_HAS_CUDA_ISTFT=1 ENGINE_HAS_CUDA_TORCH_RANDOM=1) target_compile_definitions(engine_runtime PUBLIC ENGINE_HAS_CUDA_ISTFT=1 ENGINE_HAS_CUDA_TORCH_RANDOM=1) target_link_libraries(engine_runtime PRIVATE CUDA::cudart CUDA::cufft) diff --git a/README.md b/README.md index 7fa9c15e..61d368ec 100644 --- a/README.md +++ b/README.md @@ -272,6 +272,16 @@ scripts/build_linux.sh --backend cpu --target audiocpp_cli --target audiocpp_ser The script writes to aligned build directories such as `build/linux-cuda-release`, `build/linux-vulkan-release`, `build/linux-hip-release`, and `build/linux-cpu-release`. +Without `--cuda-arch`, CUDA builds use the portable arch list (works on many +GPUs, slower to build). For a faster build targeting only the local GPU, pass +`--cuda-arch native` (CMake >= 3.24; on older CMake this falls back to the +portable list) or an explicit arch list: + +```bash +scripts/build_linux.sh --backend cuda --cuda-arch native --target audiocpp_cli --target audiocpp_server +scripts/build_linux.sh --backend cuda --cuda-arch "86;89" --target audiocpp_cli --target audiocpp_server +``` + Composite examples: ```bash diff --git a/docs/build/linux.md b/docs/build/linux.md index e52e5061..06a12b70 100644 --- a/docs/build/linux.md +++ b/docs/build/linux.md @@ -30,6 +30,15 @@ CUDA: cmake -S . -B build -DENGINE_ENABLE_CUDA=ON ``` +Without `CMAKE_CUDA_ARCHITECTURES`, the portable default arch list is built: +works on many GPUs, but builds slower. To build only for the local GPUs, +`native` is recommended (CMake >= 3.24; on older CMake it falls back to the +portable list) or set an arch manually. + +```bash +cmake -S . -B build -DENGINE_ENABLE_CUDA=ON -DCMAKE_CUDA_ARCHITECTURES=native +``` + CMake picks the first `nvcc` on `PATH`, which is often **not** the toolkit you want: distro packages install an old one to `/usr/bin/nvcc` (Ubuntu 22.04's `nvidia-cuda-toolkit` is CUDA 11.5) while the toolkit from NVIDIA lands in @@ -54,20 +63,11 @@ readelf -d build/bin/audiocpp_server | grep NEEDED | grep cuda # want libcudart.so.12 / libcublas.so.12 — libcudart.so.11.0 means a mixed build ``` -Leaving `CMAKE_CUDA_ARCHITECTURES` unset does **not** reliably build for the GPUs -present at build time on this codebase, even though ggml's own `ggml-cuda/CMakeLists.txt` -implements exactly that native-detect fallback. The reason: this project's top-level -`CMakeLists.txt` calls `enable_language(CUDA)` itself, before ggml's subdirectory is -processed — CMake computes its own default `CMAKE_CUDA_ARCHITECTURES` at that point, so -ggml's fallback logic (gated on the variable still being undefined) never runs. Verified -directly: on an RTX 5060 (sm_120), leaving the flag unset made CMake default to bare `75` -(Turing) even though `CMAKE_CUDA_ARCHITECTURES_NATIVE` was correctly autodetected as -`120a-real` in the same configure log — computed but never used. Any -`__CUDA_ARCH__`-gated kernel path newer than the default silently compiles *out*, not -merely unoptimized. **Always pass `CMAKE_CUDA_ARCHITECTURES` explicitly.** - -Note that CMake caches the CUDA compiler: switching toolkits in an -existing build directory requires deleting `CMakeCache.txt` and `CMakeFiles/`. +Leave `CMAKE_CUDA_ARCHITECTURES` unset to build the portable default arch list +(works on many GPUs, slower to build); set it to `native` (CMake >= 3.24) to +build only for the GPUs present at build time. Note that CMake caches the CUDA +compiler: switching toolkits in an existing build directory requires deleting +`CMakeCache.txt` and `CMakeFiles/`. Old CUDA GPUs (cm<89): diff --git a/docs/docker.md b/docs/docker.md index 2ea4a52b..71f8652d 100644 --- a/docs/docker.md +++ b/docs/docker.md @@ -65,6 +65,12 @@ Build with a specific CUDA version, for example 13.3.0: docker build -f .devops/cuda.Dockerfile -t local/audio.cpp:full-cuda13 --build-arg CUDA_VERSION=13.3.0 . ``` +Build for a specific set of GPU architectures (e.g. for faster, less portable builds): + +```bash +docker build -f .devops/cuda.Dockerfile -t local/audio.cpp:full-cuda12 --build-arg CUDA_DOCKER_ARCH="86;89" . +``` + ### CPU ```bash diff --git a/scripts/build_linux.sh b/scripts/build_linux.sh index 8b710636..f3381064 100755 --- a/scripts/build_linux.sh +++ b/scripts/build_linux.sh @@ -350,7 +350,7 @@ echo "Using generator: $GENERATOR" echo "Using build dir: $BUILD_DIR" echo "Including CUDA backend: $ENGINE_ENABLE_CUDA" if [[ "$ENGINE_ENABLE_CUDA" == "ON" ]]; then - echo "CUDA architectures: ${CUDA_ARCH:-}" + echo "CUDA architectures: ${CUDA_ARCH:-}" fi echo "Including Vulkan backend: $ENGINE_ENABLE_VULKAN" echo "Including HIP backend: $ENGINE_ENABLE_HIP"