From 559c9091d1bc7757183c782c35b6cb2cde5fd63c Mon Sep 17 00:00:00 2001 From: mdzurick Date: Fri, 8 May 2026 22:42:40 +0000 Subject: [PATCH 1/4] Reuse LLVM build tree for MLIR python bindings The wheel container previously re-ran build_llvm.sh per Python version with python-bindings added, which reconfigured from scratch and recompiled the full LLVM/MLIR/clang/lld tree (~5,800 objects, ~65 min). The manylinux base already builds and retains /llvm-project/build without bindings. Add a focused script that reuses that tree, flips MLIR_ENABLE_BINDINGS_PYTHON on, and runs only the install-MLIRPythonModules and install-mlir-python-sources targets. nanobind is still rebuilt per Python since it ABI-binds to the interpreter. Signed-off-by: mdzurick --- docker/build/devdeps.manylinux.Dockerfile | 7 +- docker/release/cudaq.wheel.Dockerfile | 8 +-- scripts/build_mlir_python_bindings.sh | 80 +++++++++++++++++++++++ 3 files changed, 89 insertions(+), 6 deletions(-) create mode 100755 scripts/build_mlir_python_bindings.sh diff --git a/docker/build/devdeps.manylinux.Dockerfile b/docker/build/devdeps.manylinux.Dockerfile index 81b161ad515..5e957649acb 100644 --- a/docker/build/devdeps.manylinux.Dockerfile +++ b/docker/build/devdeps.manylinux.Dockerfile @@ -83,14 +83,17 @@ RUN curl -L https://github.com/Kitware/CMake/releases/download/v3.28.4/cmake-3.2 # Build the the LLVM libraries and compiler toolchain needed to build CUDA-Q. ADD ./scripts/build_llvm.sh /scripts/build_llvm.sh +ADD ./scripts/build_mlir_python_bindings.sh /scripts/build_mlir_python_bindings.sh ADD ./cmake/caches/LLVM.cmake /cmake/caches/LLVM.cmake ADD ./tpls/customizations/llvm/ /tpls/customizations/llvm/ RUN LLVM_PROJECTS='clang;lld;mlir' LLVM_SOURCE=/llvm-project \ LLVM_CMAKE_CACHE=/cmake/caches/LLVM.cmake \ LLVM_CMAKE_PATCHES=/tpls/customizations/llvm \ bash /scripts/build_llvm.sh -c Release -v - # No clean up of the build or source directory, - # since we need to re-build llvm for each python version to get the bindings. + # The build directory at /llvm-project/build is intentionally retained: + # build_mlir_python_bindings.sh reuses it in the wheel container to add + # the python-binding targets per Python version without recompiling + # the rest of the LLVM/MLIR/clang/lld tree. # Install CUDA diff --git a/docker/release/cudaq.wheel.Dockerfile b/docker/release/cudaq.wheel.Dockerfile index d3e9956f375..52518db9d8c 100644 --- a/docker/release/cudaq.wheel.Dockerfile +++ b/docker/release/cudaq.wheel.Dockerfile @@ -51,11 +51,11 @@ RUN --mount=from=ccache-data,target=/tmp/ccache-import,rw \ fi RUN echo "Building MLIR bindings for python${python_version}" && \ CCACHE_DISABLE=1 python${python_version} -m pip install --no-cache-dir numpy "nanobind>=2.9.0" && \ - rm -rf "$LLVM_INSTALL_PREFIX/src" "$LLVM_INSTALL_PREFIX/python_packages" && \ + rm -rf "$LLVM_INSTALL_PREFIX/python_packages" && \ Python3_EXECUTABLE="$(which python${python_version})" \ - LLVM_PROJECTS='clang;lld;mlir;python-bindings' \ - LLVM_CMAKE_CACHE=/cmake/caches/LLVM.cmake LLVM_SOURCE=/llvm-project \ - bash /scripts/build_llvm.sh -c Release -v + LLVM_SOURCE=/llvm-project \ + LLVM_INSTALL_PREFIX="$LLVM_INSTALL_PREFIX" \ + bash /scripts/build_mlir_python_bindings.sh # Build wheel using unified wheel build script RUN cd /cuda-quantum && \ diff --git a/scripts/build_mlir_python_bindings.sh b/scripts/build_mlir_python_bindings.sh new file mode 100755 index 00000000000..7907f5aa322 --- /dev/null +++ b/scripts/build_mlir_python_bindings.sh @@ -0,0 +1,80 @@ +#!/bin/bash + +# ============================================================================ # +# Copyright (c) 2022 - 2026 NVIDIA Corporation & Affiliates. # +# All rights reserved. # +# # +# This source code and the accompanying materials are made available under # +# the terms of the Apache License 2.0 which accompanies this distribution. # +# ============================================================================ # + +# Builds the MLIR Python bindings against an existing LLVM build tree and +# installs them into LLVM_INSTALL_PREFIX. Requires the manylinux devdeps +# base image, where build_llvm.sh has already configured and built the +# LLVM/MLIR libraries (without python-bindings) at LLVM_SOURCE/build. +# +# This avoids reconfiguring/rebuilding the full LLVM tree once per Python +# version: only the binding objects (linked against the active interpreter +# ABI) and a small amount of tablegen output get produced. +# +# Required environment: +# LLVM_SOURCE path to the llvm-project source (default: /llvm-project) +# LLVM_INSTALL_PREFIX install destination (default: /usr/local/llvm) +# Python3_EXECUTABLE interpreter to bind against +# NANOBIND_INSTALL_PREFIX nanobind install dir (default: /usr/local/nanobind) +# +# Usage: +# Python3_EXECUTABLE=$(which python3.11) bash scripts/build_mlir_python_bindings.sh + +set -e + +LLVM_SOURCE=${LLVM_SOURCE:-/llvm-project} +LLVM_INSTALL_PREFIX=${LLVM_INSTALL_PREFIX:-/usr/local/llvm} +NANOBIND_INSTALL_PREFIX=${NANOBIND_INSTALL_PREFIX:-/usr/local/nanobind} +LLVM_BUILD_FOLDER=${LLVM_BUILD_FOLDER:-build} + +if [ -z "$Python3_EXECUTABLE" ]; then + echo "Python3_EXECUTABLE must be set." + exit 1 +fi + +llvm_build_dir="$LLVM_SOURCE/$LLVM_BUILD_FOLDER" +if [ ! -f "$llvm_build_dir/CMakeCache.txt" ]; then + echo "Expected pre-configured LLVM build at $llvm_build_dir." + echo "This script must run on top of the manylinux devdeps base image." + exit 1 +fi + +this_file_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# nanobind links against the active interpreter ABI; build it if missing. +if [ ! -d "$NANOBIND_INSTALL_PREFIX" ] || [ -z "$(ls -A "$NANOBIND_INSTALL_PREFIX"/* 2>/dev/null)" ]; then + echo "Building nanobind..." + cd "$this_file_dir/.." && repo_root=$(git rev-parse --show-toplevel) && cd "$repo_root" + git submodule update --init --recursive --recommend-shallow --single-branch tpls/nanobind + mkdir -p tpls/nanobind/build && cd tpls/nanobind/build + cmake -G Ninja ../ \ + -DCMAKE_INSTALL_PREFIX="$NANOBIND_INSTALL_PREFIX" \ + -DPython3_EXECUTABLE="$Python3_EXECUTABLE" \ + -DNB_TEST=False + cmake --build . --target install --config Release +fi + +cd "$llvm_build_dir" + +# Reconfigure the existing build tree: enable MLIR python bindings and +# point at the active interpreter. Other cache entries (compiler flags, +# enabled projects, ccache launcher, build type) are preserved. +echo "Reconfiguring LLVM build for python bindings (Python: $Python3_EXECUTABLE)..." +cmake . \ + -DMLIR_ENABLE_BINDINGS_PYTHON=ON \ + -DPython3_EXECUTABLE="$Python3_EXECUTABLE" \ + -Dnanobind_DIR="$NANOBIND_INSTALL_PREFIX/nanobind/cmake" + +# Build and install only the python-binding components. Other targets in +# the existing tree are left untouched and remain installed at +# LLVM_INSTALL_PREFIX from the base image. +echo "Building and installing MLIR python bindings..." +ninja install-MLIRPythonModules install-mlir-python-sources + +echo "Installed MLIR python bindings into $LLVM_INSTALL_PREFIX." From 9c85c31b1cb1940ec4467d0b3d94851d8ba02c14 Mon Sep 17 00:00:00 2001 From: mdzurick Date: Sun, 10 May 2026 15:34:23 +0000 Subject: [PATCH 2/4] fix annobind dir Signed-off-by: mdzurick --- scripts/build_mlir_python_bindings.sh | 31 +++++++++++---------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/scripts/build_mlir_python_bindings.sh b/scripts/build_mlir_python_bindings.sh index 7907f5aa322..6fafc45f3ad 100755 --- a/scripts/build_mlir_python_bindings.sh +++ b/scripts/build_mlir_python_bindings.sh @@ -18,10 +18,10 @@ # ABI) and a small amount of tablegen output get produced. # # Required environment: -# LLVM_SOURCE path to the llvm-project source (default: /llvm-project) -# LLVM_INSTALL_PREFIX install destination (default: /usr/local/llvm) -# Python3_EXECUTABLE interpreter to bind against -# NANOBIND_INSTALL_PREFIX nanobind install dir (default: /usr/local/nanobind) +# LLVM_SOURCE path to the llvm-project source (default: /llvm-project) +# LLVM_INSTALL_PREFIX install destination (default: /usr/local/llvm) +# Python3_EXECUTABLE interpreter to bind against; must have nanobind and +# numpy already installed (e.g. via pip). # # Usage: # Python3_EXECUTABLE=$(which python3.11) bash scripts/build_mlir_python_bindings.sh @@ -30,7 +30,6 @@ set -e LLVM_SOURCE=${LLVM_SOURCE:-/llvm-project} LLVM_INSTALL_PREFIX=${LLVM_INSTALL_PREFIX:-/usr/local/llvm} -NANOBIND_INSTALL_PREFIX=${NANOBIND_INSTALL_PREFIX:-/usr/local/nanobind} LLVM_BUILD_FOLDER=${LLVM_BUILD_FOLDER:-build} if [ -z "$Python3_EXECUTABLE" ]; then @@ -45,19 +44,13 @@ if [ ! -f "$llvm_build_dir/CMakeCache.txt" ]; then exit 1 fi -this_file_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -# nanobind links against the active interpreter ABI; build it if missing. -if [ ! -d "$NANOBIND_INSTALL_PREFIX" ] || [ -z "$(ls -A "$NANOBIND_INSTALL_PREFIX"/* 2>/dev/null)" ]; then - echo "Building nanobind..." - cd "$this_file_dir/.." && repo_root=$(git rev-parse --show-toplevel) && cd "$repo_root" - git submodule update --init --recursive --recommend-shallow --single-branch tpls/nanobind - mkdir -p tpls/nanobind/build && cd tpls/nanobind/build - cmake -G Ninja ../ \ - -DCMAKE_INSTALL_PREFIX="$NANOBIND_INSTALL_PREFIX" \ - -DPython3_EXECUTABLE="$Python3_EXECUTABLE" \ - -DNB_TEST=False - cmake --build . --target install --config Release +# Locate the pip-installed nanobind's CMake config. nanobind ships its own +# cmake module via pip, so we don't need to build it from the submodule. +nanobind_cmake_dir=$("$Python3_EXECUTABLE" -m nanobind --cmake_dir) +if [ ! -d "$nanobind_cmake_dir" ]; then + echo "Could not locate nanobind cmake dir from $Python3_EXECUTABLE." + echo "Install with: $Python3_EXECUTABLE -m pip install 'nanobind>=2.9.0'" + exit 1 fi cd "$llvm_build_dir" @@ -69,7 +62,7 @@ echo "Reconfiguring LLVM build for python bindings (Python: $Python3_EXECUTABLE) cmake . \ -DMLIR_ENABLE_BINDINGS_PYTHON=ON \ -DPython3_EXECUTABLE="$Python3_EXECUTABLE" \ - -Dnanobind_DIR="$NANOBIND_INSTALL_PREFIX/nanobind/cmake" + -Dnanobind_DIR="$nanobind_cmake_dir" # Build and install only the python-binding components. Other targets in # the existing tree are left untouched and remain installed at From bc91d0ca7e5fd285b0ac972374e84eae93059cf2 Mon Sep 17 00:00:00 2001 From: mdzurick Date: Mon, 11 May 2026 14:25:25 +0000 Subject: [PATCH 3/4] also regenerate cmake exports Signed-off-by: mdzurick --- scripts/build_mlir_python_bindings.sh | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/scripts/build_mlir_python_bindings.sh b/scripts/build_mlir_python_bindings.sh index 6fafc45f3ad..e3220bbdecb 100755 --- a/scripts/build_mlir_python_bindings.sh +++ b/scripts/build_mlir_python_bindings.sh @@ -64,10 +64,15 @@ cmake . \ -DPython3_EXECUTABLE="$Python3_EXECUTABLE" \ -Dnanobind_DIR="$nanobind_cmake_dir" -# Build and install only the python-binding components. Other targets in -# the existing tree are left untouched and remain installed at -# LLVM_INSTALL_PREFIX from the base image. +# Build and install only the python-binding components plus the regenerated +# MLIR cmake exports. The base image's mlir-cmake-exports was installed +# before python-bindings was enabled, so its MLIRTargets.cmake omits the +# MLIRPython* targets (MLIRPythonExtension.*, MLIRPythonSources.*, +# MLIRPythonCAPI.*). Reinstalling install-mlir-cmake-exports here regenerates +# the file with those entries, which downstream find_package(MLIR) + +# add_mlir_python_common_capi_library calls (in cuda-quantum's +# python/extension/CMakeLists.txt) require. echo "Building and installing MLIR python bindings..." -ninja install-MLIRPythonModules install-mlir-python-sources +ninja install-MLIRPythonModules install-mlir-python-sources install-mlir-cmake-exports echo "Installed MLIR python bindings into $LLVM_INSTALL_PREFIX." From 62baebe274700c289422817266a365c855ccc6b2 Mon Sep 17 00:00:00 2001 From: mdzurick Date: Mon, 11 May 2026 18:58:17 +0000 Subject: [PATCH 4/4] Augment LLVM_DISTRIBUTION_COMPONENTS to include the python-binding Signed-off-by: mdzurick --- scripts/build_mlir_python_bindings.sh | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/scripts/build_mlir_python_bindings.sh b/scripts/build_mlir_python_bindings.sh index e3220bbdecb..e52c2aad47a 100755 --- a/scripts/build_mlir_python_bindings.sh +++ b/scripts/build_mlir_python_bindings.sh @@ -55,6 +55,28 @@ fi cd "$llvm_build_dir" +# Augment LLVM_DISTRIBUTION_COMPONENTS to include the python-binding install +# components. The base image's build_llvm.sh sets this variable based on +# which projects were enabled at original configure time. If python-bindings +# wasn't in LLVM_PROJECTS then, MLIRPythonModules and mlir-python-sources are +# absent from the distribution list, and LLVM's distribution-aware export +# logic (LLVMDistributionSupport.cmake) filters MLIRPython* targets out of +# the MLIRTargets export set -- even after we flip MLIR_ENABLE_BINDINGS_PYTHON +# to ON. The downstream symptom is cuda-quantum's wheel cmake configure +# failing with `get_target_property() called with non-existent target +# "MLIRPythonExtension.RegisterEverything"` (etc) from +# python/extension/CMakeLists.txt. Adding these components here makes the +# reconfigure register them and regenerates MLIRTargets.cmake with the +# MLIRPython* entries the wheel build needs. +distribution_components_arg="" +existing_components=$(grep '^LLVM_DISTRIBUTION_COMPONENTS' CMakeCache.txt | cut -d= -f2- || true) +if [ -n "$existing_components" ]; then + augmented="$existing_components" + case ";$augmented;" in *";MLIRPythonModules;"*) ;; *) augmented="${augmented};MLIRPythonModules" ;; esac + case ";$augmented;" in *";mlir-python-sources;"*) ;; *) augmented="${augmented};mlir-python-sources" ;; esac + distribution_components_arg="-DLLVM_DISTRIBUTION_COMPONENTS=$augmented" +fi + # Reconfigure the existing build tree: enable MLIR python bindings and # point at the active interpreter. Other cache entries (compiler flags, # enabled projects, ccache launcher, build type) are preserved. @@ -62,7 +84,8 @@ echo "Reconfiguring LLVM build for python bindings (Python: $Python3_EXECUTABLE) cmake . \ -DMLIR_ENABLE_BINDINGS_PYTHON=ON \ -DPython3_EXECUTABLE="$Python3_EXECUTABLE" \ - -Dnanobind_DIR="$nanobind_cmake_dir" + -Dnanobind_DIR="$nanobind_cmake_dir" \ + ${distribution_components_arg} # Build and install only the python-binding components plus the regenerated # MLIR cmake exports. The base image's mlir-cmake-exports was installed